@fluid-app/droplet-sdk

中文文档
0.2.152发布于 2026/11/12UNLICENSEDTypeScriptES Module近 30 天下载 2.3万

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

中文使用文档
文档目录8 节
  1. 安装
  2. 正在验证召回
  3. 在您执行前: 充斥着 order 商店
  4. 三个最有可能咬你的东西
  5. 读取状态和接收调节事件
  6. 活性探测器
  7. 出口
  8. 许可证

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

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

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

安装

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

pnpm add @fluid-app/droplet-sdk

正在验证召回

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 联 合 国 因此,现有的登记可以是: 未经重新登记和重新安装而通过:

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 - 看起来很完整,没有. 请检查实际采用的定义:

// `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,所以的方式是确定 安装完全持有其目前的注册是清关 按此顺序再次填充该安装:

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 返回他们的 中性形状 :

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 让 错误传播 :

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 };
},

读取状态和接收调节事件

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);
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 与原子符号检查以完成和释放). 索赔方法可以同步或按承诺:

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表示健康.

// 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,状态计划/类型,健康助手,存储类型
/nextwithFluidCallback, (中文(简体) ), withFluidWebhook, (中文(简体) ), withFluidLiveness, (中文(简体) ), withFluidReconcileEvents, (中文(简体) ), createInMemoryReconcileDedupe,调节计划/类型
/prismacreatePrismaCallbackStore
/drizzlecreateDrizzleCallbackStore ——结构打字,取出 eq 已注射, 因此此软件包从不依赖于 drizzle-orm

许可证

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

版本与发布记录

@fluid-app/droplet-sdk 共发布 153 个版本,首次发布于 2026/09/18;过去 12 个月发布了 153 个版本。

版本发布日期距今
0.2.152latest2026/11/123 天前
0.2.1512026/11/123 天前
0.2.1502026/11/069 天前
0.2.1492026/11/069 天前
0.2.1482026/11/069 天前
0.2.1472026/11/069 天前

关于 @fluid-app/droplet-sdk 的常见问题

@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 和提交记录判断。