# @fluid-app/droplet-sdk 中文文档

规范地址：https://npmcn.com/package/%40fluid-app/droplet-sdk/

版本：0\.2\.152
npm 元数据更新时间：2026-11-11T23:39:34.602Z
中文译文更新时间：2026-11-15T01:42:12.433Z
软件包许可：UNLICENSED

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40fluid-app/droplet-sdk
原文对照：https://npmcn.com/package/%40fluid-app/droplet-sdk/?lang=original
源码仓库：https://github.com/fluid-commerce/fluid

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：90.2 kB，40 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：typescript ^5
- 首次发布：2026-09-17T19:44:32.148Z
- 最新版本发布：2026-11-11T23:39:34.437Z
- 近 30 天下载量：23117（2026-10-15 至 2026-11-13，npm 官方下载统计）
- 已发布版本数：153（近 12 个月 153 个）

## 中文简介

Fluid 滴入回调和webhooks的签名验证和租赁

## 安装

```sh
npm install @fluid-app/droplet-sdk
```

也可以使用：`pnpm add @fluid-app/droplet-sdk`、`yarn add @fluid-app/droplet-sdk`、`bun add @fluid-app/droplet-sdk`

## README

# @ fluid-app/ droplet- sdk (英语)

Fluid倒计时器的签字核查和租户解决 (原始内容存档于2018-10-21). webhoks. 零运行时间依赖关系.

流体符号每次调用 `HMAC-SHA256` 结束 `{timestamp}.{body}` 发送 登记本身的核查标志 与它。 此软件包验证 ,并解决从登记中申请属于哪个租户的问题 而不是从任何有效载荷.

## 安装

在npmjs上公开,所以没有 `.npmrc`,没有符号,也没有配置 CI 或 Docker 文件中 :

```bash
pnpm add @fluid-app/droplet-sdk
```

## 正在验证召回

```ts
import { withFluidCallback } from "@fluid-app/droplet-sdk/next";
import { createPrismaCallbackStore } from "@fluid-app/droplet-sdk/prisma";

const callbackStore = createPrismaCallbackStore(
  prisma.fluidCallbackRegistration,
);

export const POST = withFluidCallback(
  {
    definitions: ["cart_item_added"],
    store: callbackStore,
    resolvePrincipal: async ({ registration }) =>
      registration.dri
        ? prisma.company.findFirst({
            where: { dropletInstallationUuid: registration.dri, active: true },
          })
        : null,
  },
  async ({ payload, principal, definition }) => {
    // `principal` is the verified tenant. `definition` is the one the
    // registration was issued for — not a field the caller declared.
    return { success: true };
  },
);
```

店需要一张桌子 普里斯玛号拆船 包裹在 `@fluid-app/droplet-sdk/schema/callback-registrations.prisma`; 符号是 存储为 a **摘要**从来没有在清晰.

## 在您执行前: 充斥着 order 商店

校验与请求中显示的代号相匹配, 并与摘要相匹配 水滴已经存储。 商店之前的安装没有,所以每个 一旦开始核查,就会拒绝回电.

`backfillCallbackTokens` 充满了它。 流体返回 `verification_token` 联 合 国 因此,现有的登记可以是: 未经重新登记和重新安装而通过:

```ts
import { backfillCallbackTokens } from "@fluid-app/droplet-sdk";

const result = await backfillCallbackTokens({
  client, // anything with listCallbacks({ page, per_page })
  store, // your CallbackTokenStore
  dri: installation.dropletInstallationUuid,
  dropletUrl: process.env.FLUID_DROPLET_URL!,
  // Every callback url this droplet registers. Matched exactly: the listing is
  // company-scoped and includes other droplets' registrations, so this is what
  // decides which tokens are yours to adopt.
  ownUrls: callbacks.map((c) => `${process.env.FLUID_DROPLET_URL}${c.url}`),
});
```

运行到每个活动安装 **在此之前** 部署被包裹的路线。 A 类 部分回填意味着它错过的设施开始被拒绝.

"被"不是"没有扔",一个计数不覆盖. 两个 一个定义的有价登记,另一个定义的无价登记 `stored: 2` 带空 `missingToken` - 看起来很完整,没有. 请检查实际采用的定义:

```ts
// `definition_name` is the field Fluid uses, and what droplet configs carry.
const expected = callbacks
  .filter((c) => c.enabled !== false)
  .map((c) => c.definition_name);

if (result.missingToken.length > 0) {
  // These registrations exist but the listing returned no token for them, so
  // they cannot be adopted. They need re-registering, not a retry.
  throw new Error(
    `${dri}: no token listed for ${result.missingToken.join(", ")}`,
  );
}

const covered = new Set(result.adopted.map((r) => r.definitionName));
const uncovered = expected.filter((d) => !covered.has(d));
if (uncovered.length > 0) {
  throw new Error(`${dri}: no stored token for ${uncovered.join(", ")}`);
}
```

要求每次安装都要如此,并将故障作为阻塞器处理 部署,以后不能修复.

还有一个限度, 是关于什么留在后面,而不是什么支票 看到了吗. `adopted` 反映当前列表,所以 stale 行不能满足 检查,但店里只有高涨, 所以老字号仍然在那里,它 将仍然验证一个符号 Fluid 不再为已注册的 已替换。 任何事物都不会因为它而被拒绝;它是一个超过历史的证书 登记.

`CallbackTokenStore` 曝光情况 `deleteForInstallation`,所以的方式是确定 安装完全持有其目前的注册是清关 按此顺序再次填充该安装:

```ts
await store.deleteForInstallation(dri);
const result = await backfillCallbackTokens({ ...opts, dri });
// then assert coverage as above
```

当定义重新注册时就这么做。 一个简单的回填就够了 否则.

## 三个最有可能咬你的东西

**`resolvePrincipal` 不得读取有效载荷.** 仅证明有效签名 _哪些登记_ 签了名——不是请求对象. 带走房客 从 `body.company.id` 或者说 `x-fluid-shop` 让房客A的地契持有人 b. 解决 `registration.dri` 返回时 `null` 如果它不解决; `null` 被视作验证失败.

**失败的路线 回答200 每一个结果.** 在取出时召回 路径不能拒绝 — Fluid 屏蔽了响应中的存储前端请求, 所以 401是辆破车 给那些路线一个 `onAuthFailure` 返回他们的 中性形状 :

```ts
onAuthFailure: () => Response.json({ tax_total: 0, lines: [] }),
```

这意味着状态告诉你什么. **警报 `[fluid-callback:…] rejected` 日志行,从不显示状态代码.**

**一个网瘾 `resolve` 必须返回 `null` 用于"未知",并只扔给 "无法发现".** 包装器对两者不同 因为Fluid 否: 4xx被记录为永久拒绝,永远不再交付,而 5xx和1个429重审. 这么说吧 `null` 答案 401, 和一个抛出的错误答案 503 通过 `onResolveError`。 。 。 .

退后是安静和昂贵的。 A 类 `resolve` 吞下自己的东西 数据库出错和返回 `null` 变成一堵永久的墙 否认--如果其中一人是-- `droplet.installed`那家公司没有排行, 他们的网游将不再解决 唯一的线索是401 让 错误传播 :

```ts
resolve: async ({ dri }) => {
  const company = await db.company.findUnique({ where: { dri } }); // may throw
  if (!company?.webhookVerificationToken) return null; // settled: no such tenant
  return { secret: company.webhookVerificationToken, principal: company.id };
},
```

## 读取状态和接收调节事件

```ts
import { createFluidInstallationClient } from "@fluid-app/droplet-sdk";

const client = createFluidInstallationClient({ installToken, baseUrl });
const result = await client.getInstallationStatus();
if (result.ok) console.log(result.status.droplet_installation_status);
```

```ts
import {
  createInMemoryReconcileDedupe,
  withFluidReconcileEvents,
} from "@fluid-app/droplet-sdk/next";
export const POST = withFluidReconcileEvents(
  async (event) => {
    await handleReconcile(event);
  },
  { secret: webhookSecret, dedupe: createInMemoryReconcileDedupe() },
);
```

使用液滴本身的网球秘诀. `dedupe` 需要; 没有默认 存储。 上面的明晰内存是每个过程,适合一个 单例。 执行原子的共享商店 `claim`, (中文(简体) ), `complete` 财务报告和审定财务报表 `release` 需要(例如Redis) `SET NX PX` 与原子符号检查以完成和释放). 索赔方法可以同步或按承诺:

```ts
type ReconcileClaim =
  | { status: "claimed"; token: string }
  | { status: "in_flight" }
  | { status: "done" };

claim(id: string, ttlMs: number): ReconcileClaim | Promise<ReconcileClaim>;
complete(id: string, token: string, ttlMs: number): void | Promise<void>;
release(id: string, token: string): void | Promise<void>;
```

`claim` 必须在解剖学上保留一个带有新标志的可用ID。 内容 `ttlMs` 只有飞行中租赁: `Math.min(inFlightTtlMs, dedupeTtlMs)`。 。 。 . 包装 `inFlightTtlMs` 默认为 6万 ms, 并且必须是正和有限度的; 保持大约 10 分钟, 宽度为 Fluid 重试,或者一个坠毁的实例的租赁 能够超过每次重试. `dedupeTtlMs` 必须 同时也是积极和有限的. 活动 索赔返回 `in_flight`:包装器响应503(被Fluid记录为永久拒绝) `{ error: "in flight" }` 不叫处理器, 所以流体复试以后。 完成交付返回 `done`: 包装器响应 200 `{ ok: true, duplicate: true }`。 。 。 . 一个扔出的权利要求返回一个可重试的500,而不运行处理器.

执行 `complete` 财务报告和审定财务报表 `release` 并行 `claim`在处理器回复后, `complete(id, token, ttlMs)` 接收 `dedupeTtlMs` 完成的保留 完成(默认为86 400 000毫秒)。 扔下来 或非正确反应, `release(id, token)` 释放权利要求,以便重试可以运行. 两种操作都必须在解剖学上检查该符号是否仍然拥有一个未过期 索赔;否则,他们什么都不做。 期满后,新的交货人可以声称其身份 并且旧的处理者无法完成或解除索赔. 仅用事件编号记录完成和释放失败并保存 处理器的响应(或一个被扔出处理器的500个).

明晰的内存商店执行所有三项业务. 它也限制租赁 由它自己负责 `inFlightTtlMs` 选项(默认为60,000毫秒)。 对于更长的处理器,设置 `inFlightTtlMs` 在包装和 `createInMemoryReconcileDedupe`。 。 。 .

在签名验证和JSON解析后,未知 `reconcile.*` 事件类型 返回 200 `{ ok: true, ignored: true }` 没有要求或呼叫处理者. 只记录事件名称 。 不对齐或缺失的事件名称仍然返回 无效有效载荷400发.

只接受原子储存: `seen`编号 :`mark` 不支持存储,因为 它们不能排除同时交付一项事件编号。 共享原子存储 (例如,Redis或具有独特标识的数据库行) 多层部署; `createInMemoryReconcileDedupe()` 每个进程和 只适合一个案例。 保持处理器同位素,包括之后的重试 TTL过期或定界入室驱逐. 签名验证前的正文限制为 1 048 576 字节; 配置 `maxBodyBytes` 更改此限制.

## 活性探测器

流体发送签名 `GET` 在你的清单 `health.path` (默认) `/api/health`每一分钟,只有2xx表示健康.

```ts
// app/api/health/route.ts
import { withFluidLiveness } from "@fluid-app/droplet-sdk/next";

export const GET = withFluidLiveness({
  check: async () => void (await db.$queryRaw`SELECT 1`),
});
```

秘密默认为 `FLUID_PROBE_SIGNING_SECRET`。 。 。 . `check` 仅运行有效签名的探测器,以 `checkTimeoutMs` (2000年违约),并回答503, `false` 或暂停。 超时时中止 `AbortSignal` 转至 `check`交给你的司机 检查从不堆积:并行的探测器共享一个检查,而一个仍在运行的计时出站检查使得探测器回答503而不启动另一个. 未签名的请求得到平板 200;被拒绝的签名最多每分钟记录一次。 Middleware 绝不能重定向或认证此路径 .

## 出口

| 子路径 | 目录 |
| --- | --- |
| `.` | `verifySignature`, (中文(简体) ), `verifyLivenessProbe`, (中文(简体) ), `tokenDigest`, (中文(简体) ), `backfillCallbackTokens`, (中文(简体) ), `describeError`, (中文(简体) ), `createFluidInstallationClient`,状态计划/类型,健康助手,存储类型 |
| `/next` | `withFluidCallback`, (中文(简体) ), `withFluidWebhook`, (中文(简体) ), `withFluidLiveness`, (中文(简体) ), `withFluidReconcileEvents`, (中文(简体) ), `createInMemoryReconcileDedupe`,调节计划/类型 |
| `/prisma` | `createPrismaCallbackStore` |
| `/drizzle` | `createDrizzleCallbackStore` ——结构打字,取出 `eq` 已注射, 因此此软件包从不依赖于 `drizzle-orm` |

## 许可证

`UNLICENSED` - 故意的。 - 故意的。 这个软件包在 npmjs 上公开,以便 独立滴盘重置器可以在没有注册符的情况下安装,即 唯一原因就是它被发表。 公共服务不是 权利:不给予使用、复制、修改或重新分配的权限.

## 常见问题

### @fluid-app/droplet-sdk 是什么？

Fluid 滴入回调和webhooks的签名验证和租赁

### 如何安装 @fluid-app/droplet-sdk？

在项目目录运行 npm install @fluid-app/droplet-sdk，或使用 pnpm add / yarn add / bun add。

### @fluid-app/droplet-sdk 支持 TypeScript 吗？

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

### @fluid-app/droplet-sdk 支持 ESM 还是 CommonJS？

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

### @fluid-app/droplet-sdk 可以商用吗？

@fluid-app/droplet-sdk 使用 UNLICENSED 许可。作者声明为 UNLICENSED，即不授予公开使用许可（与公共领域的 Unlicense 不同）。使用前需获得作者授权。以上为概括，不构成法律意见，请以许可原文为准。

### @fluid-app/droplet-sdk 的下载量有多少？

根据 npm 官方下载统计，@fluid-app/droplet-sdk 在 2026/10/15 – 2026/11/13 这 30 天内被下载 23,117 次，日均约 771 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @fluid-app/droplet-sdk 还在维护吗？

最新版本 0\.2\.152 发布于 2026/11/12（3 天前）；至今共发布 153 个版本，其中过去 12 个月发布了 153 个；首次发布于 2026/09/18。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

