@ai-matrx/realtime

中文文档
0.10.9发布于 2026/10/16MITTypeScriptESM + CJS近 30 天下载 4.2万

Matrx 客户端的 ONE Supabase 实时层: 频道命名注册、 强制频道生命周期、 默认情况下的回声压制、 以 idepotent dedup 发送后最少的一次发送、 解接命令的处理器队列、 重新连接到 shitted backoff和 a

中文使用文档
文档目录24 节
  1. 此软件包为何存在
  2. 学说
  3. 1. 压制你自己的回声——修正-发音
  4. 2. 默认执行回声抑制;选择退出是抑制路径
  5. 3. 至少一进一出
  6. 4. 手提箱从不运行在套接字路径上
  7. 5. 重联是指READ. 实时没有重播 .
  8. 6. Tab睡眠和app背景是正常的失败,而不是异域的失败
  9. 7. 一个地名渠道
  10. 8. 选择运输
  11. 9. 外国同行拥有其电线——用 wire: {mode:"raw"}
  12. 静态零事件核对表
  13. 用这个
  14. 反应
  15. 每个应用程序一个供应商,一个兄弟包如何搭载它
  16. Redux 中间软件、 tunks 和其他非反应所有者
  17. 私人频道(数据库广播)
  18. 自动保存共存
  19. 出席和打字
  20. 反应原生/博览会
  21. 诊断
  22. 入境点
  23. 移动手卷频道
  24. 许可证

@ai-matrx/realtime

Matrx客户端的 ONE Supabase 实时层. 手写 .channel( 在一个 消费应用是一个代码审查缺陷.

pnpm add @ai-matrx/realtime

此软件包为何存在

"我们整个系统都为此而奋斗" 我们一直挣扎着想建立它 给留言,尝试设置笔记。 直到今天,经过数百小时 ——亚鲁曼,2026-08-29

实时不难,因为签名难。 下结为四行. 很难 因为有8个东西是无形的,直到它们受伤为止 每一个东西都是在这里学的 昂贵的方式——一个被冻结的浏览器,一个失去编辑的注释,一个显示"Ada"的聊天 打了一个小时 这个软件包写下OnCE的八件事,作为代码 让每个消费者都使用.

下面列举的是理论。 它取代了以前生活于 垫口前端 supabase-realtime 技能;技能现在指向这里.


学说

1. 压制你自己的回声——修正-发音

Supabase给你发了自己的信. Postgres 更改没有“ 无法自我” 选项 。 这个 你的回声 UPDATE 来了 50至500毫秒后 您的 REST 响应已经返回了新的 排队 降落时,任何"在飞行中拯救"的旗帜已经清除. 仅使用国旗的压制 总是想念回声.

未压制回放的回放能做什么: 保存 N 的回放在用户输入 N+1 时到达 ~ 脏-局部服务器 - 假冲突 - 冲突 UI 做 O( n2) diff work each compare- 下一个拯救它 —— 它的回声重新武装它。 自我维持主行道饱和.

Over-suppression所做的(幻相冲突,2026-09-13):每个平台表的 version 在 Every 更新上移动——桌面同步印章 file_path,一个食用的工作写作 元数据。 同样的演员,相同的内容, 我们的写待: 旧分类账称之为回声, 客户端保留了旧的号码, 而它的下个比较和扫描丢失了 用于文本等于客户端自身基础的行. 客户已经压制了一个事件 上面有它需要的号码.

包的书写分类账 分类每个有效载荷, 第一修订版 (这行的) version,从 revisionField默认值 version:

  • R1. 比我们持有的旧版本 暂停。 。 。 .
  • R2. 修订我们未解决的书写将产生匹配的内容 拥有- echo (比REST反应的回声,以数字识别).
  • R3. 与匹配内容相匹配的已经确定的修订 拥有- echo; 差异 ; 偏僻.
  • R4型导弹. 任何更新的 总是遥远的 ——同一演员,内容相同,待写与否. 交付是无害的;吞食是下一场幽灵冲突.

只有当没有修订时,时间戳原则才适用:

  1. 严格年龄 updated_at 而不是我们所持有的 暂停,丢入(合并) 倒退 updated_at,它失败了下一个保存的乐观锁入了粘滞的虚假冲突.
  2. 内容我们已知已经写出来但尚未解决 拥有- echo。 。 。 .
  3. 平等 updated_at - 只有内容指纹也匹配时,才能自选;a 同一兆位合作者 写着仍然土地.
  4. 无法解开的时间戳 – 交付. 屈从于送货,从不默不作声地下降.

使用自己的写作号码注册: ledger.begin({ …, revision: expectedVersion + 1 }), (中文(简体) ), ledger.settle(ticket, { revision: response.version }), (中文(简体) ), ledger.observe({ …, revision })。 。 。 . 和它配对 @ai-matrx/data是因为 guardedUpdate({ rebase }){\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}所以客户还是有点颠簸 已丢失( 已睡标签) 将按当前数字重试, 而不是显示为冲突 .

广播回声由信封处理: 每封信封都带有客户端的会话 ID 和 接收器会自己掉下来.

2. 默认执行回声抑制;选择退出是抑制路径

echoSuppression: false 对于真正需要回溯自身事件的稀有消费者来说 在电线上。 这绝不是默认的,也绝不是其他事情的补救.

3. 至少一进一出

重新订阅、重新加入 以及一个播放一行节目的消费者 同时INSERT也提供 同一逻辑事件两次。 供应a eventKey (或接受默认)和包拆分.

4. 手提箱从不运行在套接字路径上

一个Supabase频道在套接字的回调路径上呼叫您的处理器. 发送的处理器 至 Redux , diffs 内容, 或使 RPC 背向执行, 当一个 暴风雨来了 标签也冻结了 每个历史 Matrx 浏览器锁定是一个变体 此; 以500个已排队事件唤醒的背景标签是经典触发 .

软件包在套接字和您的处理器之间设置了有边框的、命令的和出让的队列 . 每个频道都保留顺序(在下次事件开始前等待一个自动处理器) 每几毫秒向事件循环排出量,深度被封顶——溢出 流出最古老的活儿,说得没错,并引发和解,这是正确的恢复 因为"我们跟不上".

5. 重联是指READ. 实时没有重播 .

插座倒下时发生的每起事件 都会永远消失 没有连接 重新读取会留下一个永久错误的屏幕,看起来完全健康. 所以每个频道 声明 onBackfill,而包 调用它 每个 恢复路径: 重新连接, 标签 醒来,网络恢复,队列排出。 无频道 onBackfill 得到一个大声的警告.

后退是焦躁不安的,有一个 稳定重设: 仅在 频道有 举行 30多岁,永远不要 SUBSCRIBED。在订阅器上重置会扇动 1s层的频道循环将永远进行,每个周期都会发射一个完整的接取.

6. Tab睡眠和app背景是正常的失败,而不是异域的失败

一个背景标签被拖住, 直到套接字闲置; 一个被悬停的电话又回来了 还有一个WebSocket 看着 并且永远不会再出现。 两张地图都进入 同样的醒悟之路: 和解,并重新加入任何不明显健康的东西。 网络使用 visibilitychange; 反应原生用途 AppState (单位:千美元)@ai-matrx/realtime/native) (中文(简体) ).

7. 一个地名渠道

苏帕克的 client.channel(topic) 按专题分列的去向: 两个都选择的特性 "messages" 默默地分享一道并失去对方的束缚. 宣布一个命名空间 相反的,对同名的另一种不同的声明会抛出.

另外,反应19双进取效应和 removeChannel 是aync,所以可以重载 交出了上一个挂载的仍然连接的通道 .on(...) 掷出 "不能添加 posgres changes 订阅后的回调( )。 。 。 .

——TOPIC-SEMANTICS SPLIT——两种运输方式的"主题"并非同一种意思,而且 软件包对他们不同.

  • Postgres Changes:该话题只是客户端的连接密钥. 服务器推着 已声明过滤器, 无论频道名称如何 。 所以,每一个连接尝试得到一个 独一无二 实例主题 (单位:千美元)mx:notes:u1#7)),这使得重载碰撞无法免费上方.
  • 广播和现场:主题是会议室. 是服务器粉丝的路由键 发送消息。 一个独特的实例主题并不花费什么——它花费 所有的东西数字 : 两个同时声明的浏览器 mx:room:1 土地在 #1 财务报告和审定财务报表 #2 而永不相见. (以0.1.x为单位发运,由 @ai-matrx/messaging; 固定在0.2.0. (中文(简体) )

因此,一个频道 携带广播或存在,它的 在线上逐字宣布主题,以及 管理者保留 每个已宣布的题目,一个基本通道,重新计算: 秒数 open() 联 合 国 主题将其绑定在同一个频道上,频道只有在 最后一个持有人关闭。 重载碰撞是在那里通过推迟真正的拆卸来处理的 转身,所以React的清理 时再运行 重接 而不是赛跑一个Aync removeChannel。 。 。 .

在你打开两个持有者关于一个话题之前,值得知道的后果:

  • 他们共享一个频道的配置。 第一个 echoSuppression 和存在键胜出,和 a 分歧很大 channel.room.config-mismatch 诊断——并不是一个默默的惊喜.
  • 他们在当地互相听到. 同为 Tab 的持有者共享一个客户端 ID 并打开频道 broadcast.self:false所以电线从不回响一个持枪者的相框到它的兄弟姐妹身上 经理 手数 send() 直接发送给该主题的其他开放持有者(相同的信封,相同) dadup; 永不返回发送方; 不包括生电路通道). 跨塔布交付不变.
  • 其中一个就是幕后主使. handle.isLocalLeader() 对 仅开放时间最长的持有人,以及 onLeaderChange(isLeader) 领导力移动时点火,因为 头儿关门了 在领导人身上进行一次性工作(自选、CRDT东道主选举)——两次 持有者每个结论 "我是孤独的,我是东道主" 是两个自动的,最后的snapshot-wins bug.
  • 他们共享一个存在条目(每个频道一个密钥),所以一个话题需要完全一个存在 出版社。 只读取名册的表面——打字员标签、在线点名单——通过 presence: { publish: false } (或 publish: false 改为 useTyping它看到每个人,停下来 争取进入。 两个出版商很吵 channel.room.presence.multiple-publishers 警告,不是一个谜.
  • 每个客户端仍然使用回声抑制,所以一个持有者使用 没有 看它的兄弟们自己的 send() ——这是回声压制的要点,不是房间故障. 两个持有者所看到的 每个 远程 同行信息 .
  • 将 Postgres 带入已合并房间的持单人更改绑定符迫使重建(那些 装订在JOIN有效载荷上,以后无法添加). 现通知如下: channel.room.rebuild,每个持有者通过回填门调节. 打开混合 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}频道的持有者一起在山上 {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}绝对不会发生.

8. 选择运输

  • 邮资变化 ——RLS必须出闸送出(注,文件,任务)的可持久行. 这个 默认实体同步 .
  • 广播 - 无授权微分的电平或高频数据(CRDT更新, 打字、信号. 永远不要手动播放一行您同时INSERT ——每个订阅者 得到它两次.
  • 出席情况 - 谁来了 唯以法相为相.
  • 每个功能一个频道. Postgres 更改、广播和存在可共享一个;不 运行3.
  • 如果桌子上有的话 updated_by (平台) _stamp_actor 触发器),每个更新已 识别它的编辑器——你可能根本不需要在场.

9. 外国同行拥有其电线——用 wire: {mode:"raw"}

每一次播放这个软件包都用 Matrx 信封包 (单位:千美元){v, cid, eid, ts, data}),而这个信封使得回声抑制和去除成为可能 一点也不 有些频道无法选择: 另一端是 a 单独发布的程序 它读取了它定义的有效载荷形状, 并重塑它下面的电线 只会阻止对等 在两端都听到我们的声音 产生这种模式的案例是: Chrome 扩展名 matrx-extension-bridge:<userId> 频道 - 已部署的扩展解析 a 赤线 BridgeEnvelope (单位:千美元){direction, action, requestId, payload, timestamp})有"佐德计划".

const bridge = defineChannelNamespace({
  namespace: "extension-bridge",
  parts: ["userId"],
  description: "matrx-extend ↔ matrx-frontend RPC bridge (foreign wire).",
  foreignTopic: "matrx-extension-bridge", // the peer's topic root, used verbatim
});

manager.open({
  topic: bridge.topic({ userId }),
  wire: { mode: "raw", acceptEchoFromSelf: true },
  broadcast: [{ event: "FRONTEND_RPC", onMessage: ({ data }) => route(data) }],
  eventKey: (_source, payload) => `${payload.direction}:${payload.requestId}`,
  onBackfill: () => {},
});

原始模式在两个方向上都意味着两件事: 把你的有效载荷放在电线上 逐字记录 (与手卷相同的字节) channel.send),以及 从不读取 信封 甚至连装满外壳的载荷 都不行. BroadcastMessage.enveloped 总是 false 在这里.

这是一个命名的降级,它从来不沉默. 信封上有两个保护装置:

  • 回声抑制 使用过的 cid生线没有 供应 wire.isOwnMessage 当 等同位素形状识别它的发送者; 否则设置 wire.acceptEchoFromSelf: true 状态 服务器的代码 broadcast.self:false 这是唯一的保护。 供应既不合法 并产生 channel.raw.echo-unguarded (警告);明确选择退出者仍宣布自己为 channel.raw.echo-accepted (info),因为诊断流必须显示运行的频道 无人守护.
  • 编剧 键入已关闭 eid没有它,每一次到达都是个明显的关键 供应 eventKey - 这个 接受原始有效载荷——或 channel.raw.dedup-unguarded (标.

foreignTopic 是同一个想法的主题的一半. 名称空间仍然被宣布 已检查相撞;只有 mx: 根被对等者所取代。 将一个外国主题重命名为 mx:… 不迁移任何东西——它把客户端放在一个房间里,而对等者则保持 总是在听.

原始模式为 自愿过渡它的存在是为了让外国铁丝网的两端可以骑着它 今天的软件包。 当同龄人发回信封时,更改将删除 wire 从 spec ——协调两版,并记录同行频道合同中的顺序.

静态零事件核对表

编译但一无所获的订阅有:

  1. 这表不在 supabase_realtime 出版物。 。 。 .
  2. RLS 过滤事件( 实时是 RLS 授权的 ) , 这是一种功能, 但授予门 (b) 交付).
  3. REPLICA IDENTITY FULL 缺少, 您需要旧行数据 或 RLS- on- DELETE .
  4. 表格尚未在 PostgREST/实时缓存中(notify pgrst, 'reload schema') (中文(简体) ).

用这个

反应

import { RealtimeProvider, useChannel } from "@ai-matrx/realtime/react";
import { defineChannelNamespace } from "@ai-matrx/realtime";

// Once, beside the feature. Declaring twice with a different shape throws.
export const notesChannel = defineChannelNamespace({
  namespace: "notes",
  parts: ["userId"],
  description: "Note rows for one user",
});

function App({ supabase, userId }) {
  return (
    <RealtimeProvider client={supabase} actorId={userId}>
      <Notes userId={userId} />
    </RealtimeProvider>
  );
}

function Notes({ userId }) {
  const { status } = useChannel({
    topic: notesChannel.topic({ userId }),
    postgresChanges: [
      {
        event: "*",
        schema: "workbench",
        table: "notes",
        rowId: (row) => String(row.id),
        fingerprint: (row) => String(row.content),
        onChange: ({ row }) => applyNote(row),
      },
    ],
    // Realtime has no replay. This is how the gap gets closed.
    onBackfill: () => refetchNotes(userId),
  });

  return <StatusDot status={status} />;
}

掌上电脑可能是新关闭的每一个渲染器——订阅在频道的键上 形状,而不是处理器的身份, 最新的处理器总是运行的.

每个应用程序一个供应商,一个兄弟包如何搭载它

<RealtimeProvider> 准确的构建一个经理, 经理拥有写分类账 让乐观写作的回声可以识别 树上的第二个供货商是 第二分类账和第二套频道, 所以一个经理自己写的字看起来REMOTE到 其他费用。 Apps挂载一个.

自己做实时工作的包(PACAGE)@ai-matrx/messaging, (中文(简体) ), @ai-matrx/meet必须 因此,当主机拥有一个时,骑乘主机的经理,只用一个应用程序上载它自己的 无线 :

const hostProvidesRealtime = useIsRealtimeProviderMounted();
if (hostProvidesRealtime) return <Runtime {...props} />;
return <RealtimeProvider client={client}><Runtime {...props} /></RealtimeProvider>;

询问 useIsRealtimeProviderMounted()永远 useRealtimeManager() !== null - 经理是 当挂载的提供者等待它的 Supabase 客户端时, 和一个猜测到的软件包, 也是无效的 从它上起一个供货商 然后撕裂它, 和它下面的每一个通道 即时主持人的客户端到来.

Redux 中间软件、 tunks 和其他非反应所有者

规则 第4条 偏好使用 Redux 中间软件作为特性的订阅所有者——组件效果 重新订阅身份churn和乘以跨挂载的频道。 中间软件无法调用 钩子,所以提供者 发表 它的经理和非反应所有者阅读它。 这是一个 指向供应商所建的 ONE 管理器; 在中间软件中构建自己的是第二个 写分类账,你每写一个乐观的字 就会归类为 remote 费用a 补补.

import { subscribeToRealtimeManager } from "@ai-matrx/realtime";

// Opens on arrival, closes on departure, RE-OPENS on the new manager when the
// provider rebuilds one (the signed-in user changed). Returns a stop function.
const stop = subscribeToRealtimeManager(() => ({
  topic: notesChannel.topic({ userId: currentUserId() }),
  postgresChanges: [ /* … */ ],
  onBackfill: () => store.dispatch(fetchNotes()),
}));

currentRealtimeManager() 财务报告和审定财务报表 onRealtimeManagerChange(listener) 是下面的原始人. 阅读 currentRealtimeManager() 一次 模块负载几乎总是错误:是 null 在裁决之前,管理者将被重建 当签名用户更改时。 偏爱 subscribeToRealtimeManager。 。 。 .

光谱是 工厂 因此所有者会抓取当前用户 id / dispatch 开放时间 而不是在舱载.

私人频道(数据库广播)

授权反对的主题 realtime.messages RLS - 如何实现 realtime.broadcast_changes() 从 Postgres 触发器只向有资格的用户发送行—— 声明 private数字 :

manager.open({
  topic: schedulerChannel.topic({ userId }),
  private: true,
  broadcast: [{ event: "UPDATE", onMessage: ({ data }) => apply(data) }],
  onBackfill: () => refetchSchedules(),
});

软件包创建频道 config.private: true 财务报告和审定财务报表 授权套接字 (单位:千美元)client.realtime.setAuth()签署前。 第二步不是可选的,而是 整个地表最安静的失败:没有它,就拒绝加入,就是频道 永远得不到任何东西, 它报告自己完全健康。 客户端不能 授权获得 channel.private.unsupported; 不附加的符号得到 channel.private.auth-failed。 。 。 .

自动保存共存

在共享分类账上注册写入 在此之前 乐观的更新,和回声是 一个真正的合作者写着:

const manager = useRealtimeManager();
const ticket = manager.ledger.begin({
  table: "workbench.notes",
  id: note.id,
  fingerprint: draft.content,
});
try {
  const saved = await saveNote(draft);
  manager.ledger.settle(ticket, { updatedAt: saved.updated_at, fingerprint: saved.content });
} catch (error) {
  manager.ledger.abandon(ticket);
  throw error;
}

ledger.classify(...) 返回时 own-echo | remote | stale 加号 localWritePending,所以一个 乐观的写作,它的回声,和真正的远程写作都是可以区分的.

出席和打字

const { others } = usePresence({
  topic: roomChannel.topic({ roomId }),
  presence: { state: { userId, name } },
});

const { typists, onKeystroke, stopTyping } = useTyping({
  topic: chatChannel.topic({ conversationId }),
  identity: { userId, name },
});

坠机客户端不能离开幽灵参与者(心跳+本地时长过期) "X在打字..."不能坚持(打字是续租,从来不是等待停站的锁) 可能永远到不了的信息.

反应原生/博览会

import { NativeRealtimeProvider, useChannel } from "@ai-matrx/realtime/native";

<NativeRealtimeProvider client={supabase} actorId={userId}>
  {children}
</NativeRealtimeProvider>;

相同的钩子,与 AppState 已接通的接合:背景 = 前景调节 和复相会. react-native 是一个可选的对等点;网络条目从不加载.

诊断

import { realtimeDiagnostics } from "@ai-matrx/realtime";
// or: const snapshot = useRealtimeDiagnostics();

直播频道,订阅状态,事件/分钟,以及从未有过的数字: suppressedEcho, (中文(简体) ), deduped, (中文(简体) ), stale, (中文(简体) ), queueDepth, (中文(简体) ), queueDropped, (中文(简体) ), handlerErrors, (中文(简体) ), backfills。实时错误的意思是 观察,没有猜测.


入境点

条目包含"use client"
@ai-matrx/realtime无框架核心:管理器,写出分类账,话题,存在/打字运行时间,队列,后退,脱发,诊断否(不受RSC、Redux中间软件、工人、节点的保险)
@ai-matrx/realtime/reactRealtimeProvider, (中文(简体) ), useChannel, (中文(简体) ), usePresence, (中文(简体) ), useTyping, (中文(简体) ), useRealtimeDiagnostics, (中文(简体) ), useRealtimeHealth - 加上整个核心对
@ai-matrx/realtime/nativeNativeRealtimeProvider, (中文(简体) ), createReactNativeEnvironment 加上整个反应绑定对

零运行时间依赖关系. react 财务报告和审定财务报表 react-native 是可选的同龄人。 包裹从来没有 进口 @supabase/supabase-js: 客户端在结构上被接受, 以及编译时间证明 人民群众群众 src/core/supabase-shape.test.ts。 。 。 .


移动手卷频道

  1. 声明特性的命名空间;删除手写的主题字符串.
  2. 替换 .channel(...) 块为 useChannel (或 manager.open 在中间软件.
  3. 将读取的快取移动到 onBackfill 如果模块没有,这就是你的错误 修复.
  4. 删除模块本身的回声/dedup/后退代码. 将第二个副本保存在包边 是命名失败,而不是安全选项.
  5. uniqueChannelTopic 住在这里. @ai-matrx/data/db 其再出口(数据为0.6.3) 现有呼叫站点继续工作,但移徙功能完全停止呼叫:a 命名空格名称 频道和管理者按主题语义决定有线主题 第7条之分. 一个手卷广播或现场频道正在呼叫 uniqueChannelTopic 自己却在自己的房间里分道扬镳 - 这是你正在修理的虫子,不是 行为要保护.

许可证

麻省理工学院

版本与发布记录

@ai-matrx/realtime 共发布 43 个版本,首次发布于 2026/08/31;过去 12 个月发布了 43 个版本。

版本发布日期距今
0.10.9latest2026/10/1621 天前
0.10.82026/10/1126 天前
0.10.72026/10/1126 天前
0.10.62026/10/0927 天前
0.10.52026/10/0927 天前
0.10.42026/10/0927 天前

关于 @ai-matrx/realtime 的常见问题

@ai-matrx/realtime 是什么?

Matrx 客户端的 ONE Supabase 实时层: 频道命名注册、 强制频道生命周期、 默认情况下的回声压制、 以 idepotent dedup 发送后最少的一次发送、 解接命令的处理器队列、 重新连接到 shitted backoff和 a

如何安装 @ai-matrx/realtime?

在项目目录运行 npm install @ai-matrx/realtime,或使用 pnpm add / yarn add / bun add。

@ai-matrx/realtime 支持 TypeScript 吗?

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

@ai-matrx/realtime 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ai-matrx/realtime 需要什么版本的 Node.js?

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

@ai-matrx/realtime 可以商用吗?

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

@ai-matrx/realtime 的下载量有多少?

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

@ai-matrx/realtime 还在维护吗?

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