npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lyrify/znl

v0.7.4

Published

ZNL - ZeroMQ Node Link

Readme

ZNL

基于 ZeroMQ ROUTER / DEALER 模式的 Node.js 通信库,提供:

  • 双向 RPC
  • 广播 / 订阅
  • Slave -> Master 单向推送
  • 在线状态感知
  • 心跳保活
  • 可选签名认证与透明加密
  • 内建独立 fs 文件服务

文档导航

为了让文档结构更清晰,详细内容已经拆分到独立文件:

建议阅读顺序:

  1. 本文档:快速了解项目
  2. docs/README.examples.md:先看怎么用
  3. docs/README.api.md:再查完整 API
  4. docs/README.protocol.md:最后看底层协议细节

特性

  • 基于单连接实现双向 RPC 与广播
  • 支持 Master -> SlaveSlave -> Master 双向主动请求
  • 支持 Slave -> Master 单向推送(PUSH
  • Slave 自动注册 / 注销,Master 实时维护在线节点列表
  • 支持请求超时控制与最大并发限制
  • 心跳采用 heartbeat -> heartbeat_ack 应答机制
  • Slave 提供主节点在线状态查询 API:masterOnline / isMasterOnline()
  • 内建 fs 文件服务命名空间:
    • master.fs.list/get/create/mkdir/patch/delete/rename/stat
    • master.fs.upload/download
    • slave.fs.setRoot("./")
  • fs 走独立 service 通道,不占用业务 request/response
  • 支持安全模式:
    • HMAC 签名
    • 时间戳校验
    • nonce 防重放
    • AES-256-GCM 透明加密
    • 可选 payload 摘要校验
  • 支持 authKeyMap,允许 MasterslaveId 使用不同密钥
  • Payload 支持 stringBufferUint8Array 及其数组(多帧)

安装

pnpm add @lyrify/znl

本地开发:

pnpm install

快速开始

Master

import { ZNL } from "@lyrify/znl";

const master = new ZNL({
  role: "master",
  id: "master-1",
  endpoints: {
    router: "tcp://127.0.0.1:6003",
  },
});

master.ROUTER(async ({ identityText, payload }) => {
  const text = Buffer.isBuffer(payload) ? payload.toString("utf8") : String(payload);
  return `已收到来自 ${identityText} 的消息:${text}`;
});

master.on("slave_connected", (id) => {
  console.log(`${id} 上线,当前在线:${master.slaves.join(", ")}`);
});

await master.start();

Slave

import { ZNL } from "@lyrify/znl";

const slave = new ZNL({
  role: "slave",
  id: "slave-001",
  endpoints: {
    router: "tcp://127.0.0.1:6003",
  },
});

await slave.start();

if (slave.isMasterOnline()) {
  const reply = await slave.DEALER("hello master", { timeoutMs: 4000 });
  console.log(reply.toString());
}

如果你想直接看更多完整示例,请阅读:


构造函数

new ZNL({
  role: "master" | "slave",
  id: "node-id",
  endpoints: {
    router: "tcp://127.0.0.1:6003",
  },
  maxPending: 1000,
  authKey: "",
  kdfSalt: "your-kdf-salt",
  authKeyMap: { "slave-001": "k1", "slave-002": "k2" },
  heartbeatInterval: 3000,
  heartbeatTimeoutMs: 0,
  encrypted: false,
  enablePayloadDigest: false,
  maxTimeSkewMs: 30000,
  replayWindowMs: 120000,
});

完整参数说明请查看:

性能建议:

  • 安全模式默认关闭 enablePayloadDigest
  • 如需额外的 payload 摘要一致性校验,可显式设置 enablePayloadDigest: true
  • 在高并发短消息场景下,关闭 enablePayloadDigest 通常可以获得更高吞吐

核心能力概览

双向 RPC

  • slave -> masterslave.DEALER(payload, options?)
  • master -> slavemaster.ROUTER(slaveId, payload, options?)

同时两边都支持注册自动回复处理器:

  • master.ROUTER(handler)
  • slave.DEALER(handler)

广播 / 订阅 / 推送

  • 广播:master.PUBLISH(topic, payload)
  • 订阅:slave.SUBSCRIBE(topic, handler)
  • 取消订阅:slave.UNSUBSCRIBE(topic)
  • 单向推送:slave.PUSH(topic, payload)

在线状态

  • master.slaves
  • slave.masterOnline
  • slave.isMasterOnline()

安全能力

encrypted=true 时,ZNL 启用:

  • HMAC 签名
  • 时间戳校验
  • nonce 防重放
  • AES-256-GCM 透明加密
  • 可选 payload 摘要校验

内建文件服务(fs)

ZNL 内建了一个独立于业务 RPC 的文件服务通道:

  • master.fs.* 通过内部 service 通道与指定 slave 通信
  • 不会占用或污染现有 DEALER() / ROUTER() 业务请求流
  • encrypted=true 时,fs 通道同样复用签名、防重放与透明加密机制

启用方式

slave 侧通过 slave.fs.setRoot(rootPath, policy?) 设置根目录并启用内建 fs 服务:

const slave = new ZNL({
  role: "slave",
  id: "slave-001",
  endpoints: {
    router: "tcp://127.0.0.1:6003",
  },
});

slave.fs.setRoot("./storage", {
  readOnly: false,
  allowDelete: true,
  allowPatch: true,
  allowUpload: true,
  allowedPaths: ["public/**", "configs/app.json"],
  denyGlobs: ["**/*.secret.txt", "private/**"],
  getAllowedExtensions: ["txt", "md", "json", "js", "ts", "toml", "yaml", "yml"],
  maxGetFileMb: 4,
});

await slave.start();

说明:

  • rootPath 会被解析为绝对路径,并作为所有远端文件访问的根目录
  • policy 为可选策略对象;不传时,默认允许根目录内的常规读写操作
  • 所有远端路径都必须落在 rootPath 范围内
  • 当前实现会拒绝穿过符号链接(Linux/macOS symlink)或目录联接(Windows junction)的访问,避免通过链接跳出 root
  • 如果路径命中 allowedPaths / denyGlobs 或写操作策略限制,请求会被直接拒绝
  • master.fs.upload() 会阻止“目录被文件覆盖”:当目标路径是目录时,不会执行目录删除后再写入文件的行为

policy 支持以下字段:

  • readOnly: boolean
    • 设为 true 时,拒绝所有写操作
    • 当前会拦截:create / mkdir / patch / delete / rename / upload
  • allowDelete: boolean
    • 是否允许 master.fs.delete()
    • 默认 true
  • allowPatch: boolean
    • 是否允许 master.fs.patch()
    • 默认 true
  • allowUpload: boolean
    • 是否允许 master.fs.upload()
    • 也会限制 master.fs.create()master.fs.mkdir()
    • 默认 true
  • allowedPaths: string[]
    • 可选白名单路径/模式
    • 非空时,只有命中的路径才允许访问
    • 支持目录前缀和 glob 风格,例如:"public", "public/**", "configs/*.json"
  • denyGlobs: string[]
    • 可选黑名单 glob
    • 命中后直接拒绝访问
    • 例如:"**/*.secret.txt", "private/**"
  • getAllowedExtensions: string[]
    • 限制 master.fs.get() 允许读取的文件扩展名(仅文本类文件)
    • 默认包含常见文本扩展名(如 jstxttoml 等)
    • 不在列表中的文件会被拒绝,并提示改用 master.fs.download()
  • maxGetFileMb: number
    • 限制 master.fs.get() 单文件最大读取大小(单位 MB)
    • 默认 4
    • 超出限制会报错,并提示改用 master.fs.download()

建议:

  • start() 前调用 slave.fs.setRoot(...)
  • 生产环境优先配合 encrypted=true
  • 对外提供远程文件能力时,优先使用最小权限策略,例如只读模式或显式白名单

主要 API

  • slave.fs.setRoot(rootPath, policy?)
  • master.fs.list(slaveId, path, options?)
  • master.fs.get(slaveId, path, options?)
  • master.fs.create(slaveId, path, options?)
  • master.fs.mkdir(slaveId, path, options?)
  • master.fs.patch(slaveId, path, unifiedDiff, options?)
  • master.fs.delete(slaveId, path, options?)
  • master.fs.rename(slaveId, from, to, options?)
  • master.fs.stat(slaveId, path, options?)
  • master.fs.upload(slaveId, localPath, remotePath, options?)
  • master.fs.download(slaveId, remotePath, localPath, options?)

上传/下载进度回调(options.onProgress(event)):

  • master.fs.upload()master.fs.download() 都支持可选 onProgress 回调
  • event.phase"init""chunk""complete"
  • 常用字段:directionphaseslaveIdsessionIdlocalPathremotePathtransferredtotalpercentchunkIdtotalChunkssize
  • 速率与预计时间:speedBps(字节/秒)、etaSeconds(预计剩余秒数;无法估算时为 null
  • complete 阶段额外包含 meta(完成阶段返回元信息)

upload(remotePath) 路径语义(localPath 必须是本地文件):

  • remotePath/\ 结尾时,按“目录路径”处理,自动落盘为 remotePath + basename(localPath)
    • 例如:assets/ + banner.txt => assets/banner.txt
  • remotePath. / ./ / .\ 时,按 fs root 根目录处理,自动落盘为 basename(localPath)
  • remotePath 不带结尾斜杠,但远端该路径已存在且是目录时,仍按目录处理,自动落盘为 remotePath/basename(localPath)
    • 例如:test 已存在目录时,上传 banner.txt => test/banner.txt
  • 其他情况按“明确文件路径”处理(可覆盖已有文件)
  • 出于安全考虑,上传不会把已有目录替换成文件;若最终目标是目录会直接拒绝

详细 API 和完整示例请分别查看:


文档拆分说明

当前文档只保留:

  • 项目简介
  • 快速开始
  • 核心能力概览
  • 文档入口导航

详细内容已拆分为独立文档:

1. API 参考

docs/README.api.md

适合查阅:

  • 构造参数
  • 生命周期 API
  • 双向 RPC API
  • 广播 / 订阅 / 推送 API
  • 在线状态与密钥管理 API
  • 完整 fs API
  • 事件列表
  • 返回值与使用建议

2. 使用示例与实践

docs/README.examples.md

适合查阅:

  • 最小可运行示例
  • Master / Slave 启动方式
  • 双向 RPC 示例
  • 广播、订阅、推送示例
  • 安全模式示例
  • fs 的 CRUD / patch / upload / download 示例
  • 实战组织建议与排障建议

3. 底层协议说明

docs/README.protocol.md

适合查阅:

  • 真实 ZeroMQ 外层控制帧
  • req/ressvc_req/svc_res 的区别
  • 明文模式与安全模式的差异
  • fs service 的底层承载方式
  • 抓包、协议对接、排障参考

事件

通过 node.on(eventName, handler) 监听:

| 事件 | 触发方 | 说明 | |------|--------|------| | router | Master | ROUTER socket 收到原始帧(所有类型) | | dealer | Slave | DEALER socket 收到原始帧(所有类型) | | request | 两者 | 解析出 RPC 请求帧(认证通过后) | | response | 两者 | 解析出 RPC 响应帧 | | message | 两者 | 所有解析消息的统一事件 | | publish | Slave | 收到 master 广播,携带 { topic, payload } | | push | Master | 收到 slave 推送,携带 { identityText, topic, payload } | | slave_connected | Master | slave 注册成功上线,携带 slaveId | | slave_disconnected | Master | slave 注销或发送失败下线,携带 slaveId | | auth_failed | Master / Slave | 认证失败(签名校验失败、重放检测失败、解密失败等),请求已被丢弃 | | error | 两者 | 内部错误 |

⚠️ 建议:请始终为 error 事件注册监听器(node.on("error", handler))。

Node.js 的 EventEmitter 在触发 error 且没有任何监听器时,会抛出异常并可能导致进程退出。 请在生产环境中自行监听并接入日志/告警系统。

完整事件说明请查看:

  • docs/README.api.md

本地示例

# 终端 1:启动 Master
pnpm example:master

# 终端 2:启动 Slave(可指定 ID)
pnpm example:slave
node test/slave/index.js slave-001

如需查看更多实践示例,请阅读:

  • docs/README.examples.md

集成测试

在同一进程内启动 Master / Slave,自动验证:

  • RPC
  • 并发
  • 认证
  • 超时
  • PUB/SUB
  • 心跳恢复
  • 在线状态 API
  • 内建 fs 文件服务
pnpm test

如需输出详细测试日志(包含每个用例与断言),可开启 verbose:

ZNL_TEST_VERBOSE=1 pnpm test

并发压测

明文模式

# 终端 1:启动 Echo 服务端(plain)
pnpm test:echo

# 终端 2:发起并发压测(plain)
pnpm test:100 -- 100 10000 slave-001

安全模式

# 终端 1:加密模式启动 Echo 服务端
ZNL_AUTH_KEY=my-secret ZNL_ENCRYPTED=true pnpm test:echo

# 终端 2:加密模式压测
pnpm test:100 -- 100 10000 slave-001 my-secret true

常见问题

为什么 slave.start() 后立刻发送第一条请求可能失败?

当前版本对 Dealer 的发送策略更严格。建议先等待 slave.isMasterOnline() === true,再发送首个业务请求。

更完整的启动建议请查看:

  • docs/README.examples.md

为什么会出现“令牌已过期或时间戳异常”?

常见原因:

  • 主从机器时间差过大
  • 节点时间被手动修改
  • 历史旧消息在较晚时间才被投递

协议与安全细节请查看:

  • docs/README.protocol.md

masterOnline=true 是否表示此刻网络一定可用?

不是。该值表示最近一次链路确认成功,适合作为业务层在线状态参考,但不是一次即时网络探针。


总结

如果你只想快速记住 ZNL 的核心入口,可以先记住这些:

  • 双向 RPC:DEALER() / ROUTER()
  • 广播:PUBLISH() / SUBSCRIBE()
  • 推送:PUSH()
  • 在线状态:masterOnline / isMasterOnline()
  • 文件服务:slave.fs.setRoot() + master.fs.*
  • 安全模式:encrypted: true + authKey/authKeyMap

详细内容请继续阅读: