# @dxv-systems/realtime 中文文档

规范地址：https://npmcn.com/package/%40dxv-systems/realtime/

版本：0\.3\.1
npm 元数据更新时间：2026-11-09T19:48:19.558Z
中文译文更新时间：2026-11-13T02:19:02.909Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40dxv-systems/realtime
原文对照：https://npmcn.com/package/%40dxv-systems/realtime/?lang=original
项目主页：https://github.com/dxv-systems/dxv-packages#readme
源码仓库：https://github.com/dxv-systems/dxv-packages

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

- 包体积（解压后）：38 kB，8 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=20\.0\.0
- 运行依赖：1 个
- peer 依赖：react \>=18
- 首次发布：2026-09-15T12:57:10.709Z
- 最新版本发布：2026-11-09T19:48:19.395Z
- 近 30 天下载量：2505（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：4（近 12 个月 4 个）

## 中文简介

AWS AppSync Evolutions上的实时更改信号——订阅令牌薄荷,SigV4发布,以及一个带有投票回落的平反订阅钩

## 安装

```sh
npm install @dxv-systems/realtime
```

也可以使用：`pnpm add @dxv-systems/realtime`、`yarn add @dxv-systems/realtime`、`bun add @dxv-systems/realtime`

## README

# @dxv-系统/实时

实时更改信号结束 [AWS App同步事件](https://docs.aws.amazon.com/appsync/latest/eventapi/):订阅-token mintting,SigV4发布,以及一个带有投票倒计时的平反订阅钩.

```bash
npm i @dxv-systems/realtime
```

## 原则:信号而不是数据

发布一个小的"Stale"有效载荷,让客户端通过正常的,许可检查的路径重新读取(What went stale)`router.refresh()`, (中文(简体) ), `invalidateQueries`) (中文(简体) ). 实时层不需要您的数据模型 。 套接字只会缩短延迟时间;投票仍然会限制延迟时间.

## 所需资源

您需要 AppSync 事件 API 。 配有 HS256 JWT 的订阅者 `mintSubscribeToken`。该符号是作为(否)发送的 `Bearer` 前缀)作为 `Authorization` 在 WebSocket 连接页眉和每个页眉中的值 `subscribe` 留言。 您的 API 授权方必须用同样的签名秘密来验证该符号, 并只允许在 `matchChannel(claims.ch, channel)` 这是真的。 出版使用IAM(SigV4).

## 环境变量

设定所有这些可以实时启用 。 每个服务器功能都读取它们 **懒散**,在呼叫时间,所以你的应用构建 没有它们,未配置的环境继续工作: `publish` 变成一个不,并且 `mintSubscribeToken` 返回时 `null`。 。 。 .

| 瓦尔 | 用于 | 含义 |
| --- | --- | --- |
| `REALTIME_HTTP_ENDPOINT` | 服务器, 客户端配置 | App同步事件 HTTP 端点, **仅主机** (例如 `abc.appsync-api.eu-west-1.amazonaws.com`(中文(简体) ) |
| `REALTIME_WS_ENDPOINT` | 客户端配置 | AppSync 事件实时端点, **仅主机** |
| `REALTIME_REGION` | 发布 | SigV4 区域 |
| `REALTIME_NAMESPACE` | 服务器 | 例如您的应用程序的频道命名空间 `myapp` |
| `REALTIME_SIGNING_SECRET` | 服务器 | HS256 订阅符的秘密 |
| `REALTIME_PUBLISH_ACCESS_KEY_ID` 编号 : `REALTIME_PUBLISH_SECRET_ACCESS_KEY` | 发布 | IM证书. 范围 `appsync:EventPublish` 在您的命名空间. |

## 服务器( U)`./server`,仅指节点)

```ts
import { mintSubscribeToken, realtimeClientConfig, publish } from "@dxv-systems/realtime/server";
```

-   **`mintSubscribeToken({ sub, channel, ttlSeconds? })`**: HS256 JWT 与 `node:crypto` (无) `jsonwebtoken` (b) 依赖性。 索赔: `sub`, (中文(简体) ), `ns` (从) `REALTIME_NAMESPACE`, (中文(简体) ), `ch` (单位:千美元) `channel` 你通过,这可能是一种模式,例如 `/myapp/*`, (中文(简体) ), `iat`, (中文(简体) ), `exp` (最多为 `iat + 900`,默认值900). 如果第一个部分 `channel` 没有 `ns`: 这是一个呼叫器错误, 不是运行时的条件 。 回返 `null` 当阴道被关闭时.
-   **`realtimeClientConfig({ sub, channel })`**: 满分 `{ token, expiresAt, httpEndpoint, wsEndpoint }` 您的符号路由返回浏览器, 或 `null`。 。 。 . `expiresAt` 实值 **时间( 毫秒)**而不是JWT的第二精度 `exp`,因此客户端可以将其与 `Date.now()` 直接说.
-   **`publish(channel, payload)`**页:1 `POST /event`2s超时,永远不要扔。 回返 `{ ok: true, skipped: true }` 不发送 当发布env vars 的未设置。 车身 : `{ channel, events: [JSON.stringify(payload)] }`。 。 。 .
-   **`publishMany(channels, payload)`**:粉丝出道方便. 返回第一个失败,或 `{ ok: true, skipped: true }` 只有每个频道都跳过.

`./server` 还有再出口 `matchChannel`。 。 。 .

### Token 路由( Next.js App 路由器)

```ts
// app/api/realtime/token/route.ts
import { NextResponse } from "next/server";
import { realtimeClientConfig } from "@dxv-systems/realtime/server";
import { getSession } from "@/lib/session";

export async function GET() {
  const session = await getSession();
  if (!session) return new NextResponse(null, { status: 401 });

  const config = realtimeClientConfig({ sub: session.userId, channel: "/myapp/*" });
  return NextResponse.json(config); // null when realtime isn't configured: the client just doesn't connect
}
```

### 承诺后公布( 任何节点服务器)

```ts
import { publish } from "@dxv-systems/realtime/server";

await db.transaction(async (tx) => { /* write */ });
const result = await publish("/myapp/changes", { type: "changed", paths: ["/dashboard/planning"] });
if (!result.ok) console.error("realtime publish failed", result.error); // never fails the request
```

## 异态性( N)`.`(中文(简体) )

```ts
import { matchChannel } from "@dxv-systems/realtime";
```

`matchChannel(pattern, channel)` 是您授权方应该执行的频道规则 。 无图案 `/*` 只匹配准确的频道。 结束于 `/*` 匹配前缀下的每个频道。 节点或DOM依赖,所以在任何捆绑的目标中都是安全的.

## 反应( E)`./react`, (中文(简体) ), `"use client"`,对等依赖 `react >=18`(中文(简体) )

```tsx
import { useRealtimeChannel, useLiveRefresh, defaultIsBusy } from "@dxv-systems/realtime/react";
```

### `useRealtimeChannel(channel, onEvent, { getConfig, onResync? })`

执行 [AppSync 事件 WebSocket 协议](https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-websocket-protocol.html)它连接到 `wss://<wsEndpoint>/event/realtime` 与 `aws-appsync-event-ws` 和基数64url `header-...` 子议定书,发送 `connection_init`,订阅于 `connection_ack`并重新武装一个守住的监视器 在每一个 `ka` (它关闭和重新连接 如果没有人到达内部) `connectionTimeoutMs`) (中文(简体) ). 它召唤 `onEvent` 与每枚已解析的有效载荷 `data` 留言.

`connected` 反映a **实时订阅** (单位:千美元)`subscribe_success`),而不只是打开的套接字. 重新连接使用被封顶的指数回放与焦急(1s至30s). `onEvent`, (中文(简体) ), `getConfig` 财务报告和审定财务报表 `onResync` 你不必记起他们.

断开与下一个之间发布的事件 `subscribe_success` 已丢失 : 服务器无法重新发送 . **`onResync`** 每起火灾 `subscribe_success` 在第一个钩子实例之后(每一次重新连接),你可以重新读取状态并关闭缺口。 把它线接到同样的刷新 `onEvent`数字 :

```ts
useRealtimeChannel(channel, onEvent, { getConfig, onResync: () => router.refresh() });
```

套接字被旋转后 **一小时** (单位:千美元)`MAX_CONNECTION_AGE_MS`),有计划的再连接,没有后退. 这不是一个符号刷新:授权方只运行在连接和订阅上,从不开放订阅,所以一个符号达到15分钟 `exp` 不需要新的套接字。 一个小时的上限限定了订阅能活过旋转的签名秘密的时间. `getConfig` 在每个连接上再次被调用,所以每个新连接都带有当前符号.

`getConfig` 返回时 `null` 表示"这里没有配置实时". 钩子一直被暂停`connected: false`,没有套接字,没有出错)并重试 `getConfig` 每30岁 A 类 `subscribe_error` 或者说 `broadcast_error` 日志一次通过 `console.warn` (与错误的有效载荷)在关闭和后退之前,所以一个错误的信道模式出现在控制台上.

**共享连接 :** 一个WebSocket每个应用程序,而不是每个钩子。 每个钩子在其频道上注册一个订阅者. 频道的第一个订阅者发送 `subscribe`,最后一个发送 `unsubscribe`,套接字只在频道没有订阅者时关闭。 同一频道上的两个钩子共用一个订阅,两个都接受它的活动. 第二个是 `connected` 立即得到没有 `onResync`因为它没有错过任何东西。 A 类 `subscribe_error` 仍然关闭整个套接字(通常的原因是一个已过期的符号,只有再连接才能获取一个新的符号),因此每个信道重新一起订阅,每个已经订阅的钩子都被再同步. 按登记顺序为每个连接指定订阅代号(`s1`, (中文(简体) ), `s2`(中文(简体) ). `__resetRealtimeConnection()` 仅导出用于测试 : 它放弃了模块级连接, 所以每次测试都开始干净.

### 结合两个钩子

`useLiveRefresh` 需求 `connected` 从 `useRealtimeChannel`时 `useRealtimeChannel`调用 `requestRefresh` 从 `useLiveRefresh`。通过调试打破循环 `useRealtimeChannel` 首先,通过一个回调,通过一个回调,并填补该回调从 `useLiveRefresh`返回值 .

```tsx
"use client";
import { useRef } from "react";
import { useRouter, usePathname } from "next/navigation";
import { useRealtimeChannel, useLiveRefresh } from "@dxv-systems/realtime/react";

export function LiveRefresh() {
  const router = useRouter();
  const pathname = usePathname();
  const requestRefreshRef = useRef<() => void>(() => {});

  function matches(payload: unknown) {
    const paths = (payload as { paths?: string[] })?.paths ?? [];
    return paths.some((p) => pathname.startsWith(p));
  }

  const { connected } = useRealtimeChannel(
    "/myapp/*",
    (e) => {
      if (matches(e)) requestRefreshRef.current();
    },
    {
      getConfig: () => fetch("/api/realtime/token").then((r) => (r.ok ? r.json() : null)),
      onResync: () => requestRefreshRef.current(), // a reconnect may have missed events
    },
  );

  const { requestRefresh } = useLiveRefresh({
    refresh: () => router.refresh(),
    intervalMs: 2 * 60_000,
    connectedIntervalMs: 10 * 60_000,
    connected,
    // isBusy omitted: the built-in heuristic is used
  });
  requestRefreshRef.current = requestRefresh;

  return null;
}
```

### Vite / TanStack 查询

```tsx
import { useRef } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { useRealtimeChannel, useLiveRefresh } from "@dxv-systems/realtime/react";

function useOrderEvents(orgId: string, orderId: string) {
  const queryClient = useQueryClient();
  const invalidate = () => queryClient.invalidateQueries({ queryKey: ["order", orderId] });

  const requestRefreshRef = useRef<() => void>(() => {});

  const { connected } = useRealtimeChannel(
    `/myapp/org-${orgId}/order-${orderId}`,
    () => requestRefreshRef.current(),
    {
      getConfig: () => fetch("/api/realtime/token").then((r) => (r.ok ? r.json() : null)),
      onResync: () => requestRefreshRef.current(),
    },
  );

  const { requestRefresh } = useLiveRefresh({
    refresh: invalidate,
    intervalMs: 30_000,
    connectedIntervalMs: 2 * 60_000,
    connected,
  });
  requestRefreshRef.current = requestRefresh;
}
```

### `useLiveRefresh({ refresh, intervalMs, connectedIntervalMs, connected, isBusy? })` 返回时 `{ requestRefresh }`

电话 `refresh()` 每个 `connected ? connectedIntervalMs : intervalMs`,仅在标签可见时。 当标签再次可见时,如果上次刷新时间超过30s,则立即刷新. 当用户忙的时候,它跳过一个勾选,并在15s后安排一个单次重试(不是一叠重试).

**`requestRefresh()`** 按要求运行同一门道(可见度检查,忙于检查其单行15s重试,最后再更新),所以实时事件可以在不绕过繁忙门的情况下触发刷新. 它的特性很稳定,可以直接通过 `onEvent` 或者说 `onResync`。在相距500米以内调用 组合成一个后继的刷新,所以连续数个事件导致一个刷新。 隐藏标签时的呼叫被推迟,当用户返回标签时运行,无论30s Staleness规则如何.

**默认忙碌检查** (目标) `isBusy`:一个被扣倒的指针总是算作繁忙(5分钟后被打得僵硬,被用安全网来对付被卡住的旗帜). 否则,有重点的输入、文本区域、可选择或可满足的,或开放对话框(`[role="dialog"]` 或者说 `[aria-modal="true"]`,它也匹配 Radix 对话框),只算忙 **在后一个的60个之内 `keydown`, (中文(简体) ), `input`, (中文(简体) ), `pointerdown` 或者说 `wheel`**。一个在搜索框中以 Stale 焦点为焦点的闲置墙面显示会不断刷新。 纯决定函数导出为 `shouldSkipRefresh(facts: BusyFacts)` 测试. `useLiveRefresh` 在挂载时注册其交互听器,并在卸载时将其删除;在导入时没有附加.

过 `isBusy` 替换为您自己的 `() => boolean`。 。 。 . `defaultIsBusy()` 以无国籍检查方式导出 **外部** `useLiveRefresh`。它没有交互窗口跟踪(一个普通函数不能拥有听众),因此倾向于省略 `isBusy` 过 `defaultIsBusy` 改为 `useLiveRefresh`。 。 。 .

## 模块格式

输出为普通JS. 这个 `"use client"` 指令在 `./react` 幸存下来的建筑,但土地 **之后** 页:1 `"use strict"` 普通JS输出会添加。 环绕 `./react` 在你身边 `"use client"` 组件;不依赖软件包的指令来设定边界.

## 议定书说明

-   **`data` 消息 `event` 字段.** AppSync 协议参考显示 `data` 留言的 `event` 作为 JSON 字符串的一元数组,而出版的docs 则描述了一组简单的字符串事件。 钩子既接受空弦又接受阵列 `JSON.parse`每个项目.
-   **出版头条.** `publish()` 发送 `content-type: application/json` 加上SigV4 标题 `aws4fetch` 添加(`host`, (中文(简体) ), `x-amz-date`, (中文(简体) ), `Authorization`,匹配 [事件 HTTP 发布文件](https://docs.aws.amazon.com/appsync/latest/eventapi/publish-events.html)。 。 。 .
-   **没有出版重试.** `aws4fetch` 默认情况下,重复5xx和429次答复最多10次,超过2s公布的预算. `publish()` 禁用这些重试.
-   **没有符号驱动重接 .** 套接字仅在网络滴入、 持续超时、 a `subscribe_error`或者一个小时的帽子 提前关闭以刷新符号只会创建一个事件丢失的窗口 .

## 常见问题

### @dxv-systems/realtime 是什么？

AWS AppSync Evolutions上的实时更改信号——订阅令牌薄荷,SigV4发布,以及一个带有投票回落的平反订阅钩

### 如何安装 @dxv-systems/realtime？

在项目目录运行 npm install @dxv-systems/realtime，或使用 pnpm add / yarn add / bun add。

### @dxv-systems/realtime 支持 TypeScript 吗？

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

### @dxv-systems/realtime 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @dxv-systems/realtime 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @dxv-systems/realtime 可以商用吗？

@dxv-systems/realtime 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @dxv-systems/realtime 的下载量有多少？

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

### @dxv-systems/realtime 还在维护吗？

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

