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

dsh-whale-post-bus

v0.6.1

Published

核心:信封/签名/握手/幂等/落盘 · Core: envelope, signing, handshake, idempotence, on-disk store

Readme

dsh-whale-post-bus

English: bus README in English

核心:信封 / 摘要+HMAC 签名 / 握手 / 幂等 / 落盘

  • 提供接口:whale.bus
  • 依赖接口:whale.roster、whale.types、whale.gate、whale.deliver、whale.verify (全部可选 —— 拿不到就退化成内置样例/跳过对应策略,绝不把名单写进核心)
  • 自测:node selftest.mjs(只看退出码:0 过/非 0 不过)

接口带 apiVersion;核心不认识任何成员名与类型标识 —— 名单与类型一律从接口取。

它回答哪三个问题(都写明了为什么)

  1. 核心不认识任何名字与类型 —— 名单与类型一律从接口取;拿不到接口就退化成内置样例。
  2. 默认离线 —— 离线 = 信留在对方信箱里等人来收;在线 = 立刻投出去(要叫醒对方)。 mode 取值写错一律拒发,不许悄悄降级("急件写成 ONLINE 就永远叫不醒人"这种事故我们栽过)。
  3. 只投活体;不活就不投也不消费 —— 对方没有活体会话 ⇒ 信原样留在信箱里 ⇒ 信只会晚到,不会不到。

接口

| 方法 | 一句话 | |---|---| | send(letter) | 发一封:{ as, to, subject, body, mode, type, re, force };拒发抛错(CLI 里=退出码非 0) | | pump({ as, keep, inject, reader }) | 收信(见下"收信语义") | | verify(letter) | 查信封的问题,返回问题数组(空=没问题) | | hello({ as }) | 握手;写 hello/<as>.json | | format(env) | 把信封排成给人看的文本 | | paths()/root()/keyHex()/digest(s)/seal(f)/sign(env)/loadState(as) | 底层零件(自测、工具、闸用) |

收信语义(这一段最容易被误解)

pump 默认会消费(搬进 seen/ + 写 ack)—— 但当收信人此刻没有活体会话、也没有任何东西声明"我就是读者"时,它一封都不消费: 信原样留在 inbox/(不搬 seen、不删原件、不写 ack)。

| keep | 行为 | |---|---| | 不传 | 没有读者就不消费(默认) | | true | 只看不消费 | | false | 显式消费 | | 函数 (letter) => boolean | 逐封判 |

另外:reader: true ⇒ 显式声明"我就是读者"(CLI 把信打到终端时就是这么做的); 给了 inject 就会真调它,它抛异常 ⇒ 不消费(交不出去的信,绝不当成交出去了)。

配置

| 字段 | 默认 | 含义 | |---|---|---| | root | WHALE_POST_ROOT ⇒ ./.whale-mail | 信箱根(信就落在这里) | | keyFile | <root>/signing.key | 签名密钥(首用时自动生成 32 字节) | | maxBody | 64 KiB | 正文上限 | | requireHello | true | 三态:默认 true = 照发 + 明示"降级为离线"(见下"握手");false = 不检查;'reject' = 旧版的"拒发"(老部署要旧行为就写它)。离线件根本不看握手 | | helloMaxAgeMs | 24 小时 | 握手多旧算过期 | | helloClockSkewMs | 60 秒 | 时钟容差:hello 的 sentAtMs 比"现在"还晚多少以内不算未来(机器差几秒很常见)。未来时间戳一律判不新鲜(设计文档 S6h-④:否则伪造一个 2099 年的 hello 就能让握手闸形同虚设;fail-safe:误判只许偏向离线) | | defaultType | 'direct' | 配 null ⇒ 没写类型的信直接拒发(不替调用方猜) | | offlineOnlyFlag | 不配 | 三态:不配 ⇒ 不启用;配属性名 ⇒ 照发 + 明示wakePrediction.offlineOnly(设计文档判据 58-62:不再拒发 —— "叫不醒就留箱等人,但要大声说明");offlineOnlyMode: 'reject' ⇒ 旧版的拒发 | | dormantFlag | 不配 | 对被明确标成休眠的成员 ⇒ 退信(S11):不落它信箱/进班级 退信/(没删)/留一份 *.why.txt 说明/结果里 verdict: 'bounced' + bounced 名单明示/不占配额(它在 gate.record 之前就返回)。不许根据"多久没 hello"自己猜休眠。只部分休眠(发给组)⇒ 只投醒着的 + skippedDormant |

结构化回执(2026-10-10 按设计文档加:今天的设计目标是 2026-10-06 01:5x)

一张回执说清两件事(设计文档判据 87-90):

| 字段 | 说什么 | |---|---| | recipientState | 我(收件人)当时的状态 —— online/stale-online/offline("我在不在线"=我自己发出去的 hello 新不新鲜) | | disposition | 这封信的去向 —— 见下表 |

| disposition | 什么时候 | |---|---| | accepted-online | 在线签收 | | delivered-offline-by-stale | 发件人没等到我的新鲜 hello ⇒ 已降级为离线寄达 | | delivered-offline-by-declaration | 我声明过"只收离线" ⇒ 这封在线件按离线寄达 | | delivered-offline | 本来就是离线件("降级"只对在线件有意义) | | refused | 拒收(写在"验签不过"那条路上) |

disposition 优先取发件人写下的投递说明(设计文档原话:"那才是当时怎么判的")—— 所以信封里带一个 deliveryNote('offline-only'/'no-handshake'):"发件人写下的"必须随信过去, 光看 mode 推不出"为什么走了离线"。

还有两个字段:by(谁回的)/ok(true;拒收时为 false)/note(说明)。 每份 ack 各写各的文件(<id>.<by>.ack.json —— 群发时多个收端不会撞车)。

握手(2026-10-10 按设计文档改:未握手不再拒发)

设计文档判据 1-3 +「今天的设计目标是 2026-10-06 01:5x」:"叫不醒就留箱等人,但要大声说明" 为什么"拒发"是错的:"拒发"是更早的版本 —— 而它是反的:信根本没出去, 而发信人还以为"协议不让发" —— 这跟"信只会晚到,不会不到"是拧着的。

| requireHello | 行为 | |---|---| | 其它真值(默认 true) | 照发 + 明示"对它们降级为离线" —— 返回值里 wakePrediction.willWait 列出"叫不醒、信会留在箱里等"的人(不许静默) | | false | 不检查握手(也不明示) | | 'reject' | 旧版的"拒发"(要旧行为就写它 ⇒ 老部署一字不变) |

三条配套口径:① 离线件根本不看握手(离线件躺着等人,握手管不着它); ② 握过手的人不该出现在 willWait 里(别乱报警);③ --force 照旧跳过检查。

信封与签名(想自己实现一个端,照这个来)

  • 签名域(FIELD_ORDER):v, kind, id, from, to, seq, subject, body, sha256, sentAtMs, type, mode, re, hop mode 也在里面(漏签它 ⇒ 别人能把它从离线改成在线)。
  • fail-closed 双向:seal() 见未登记字段直接抛;verify() 见未登记字段直接拒 ("加字段忘了进签名域"=那个字段可被随便改、验签照样过)。
  • 已知但已废弃的字段(LEGACY_FIELDS,2026-10-10 从共享邮局根里的真信上学到的): 班级里那套的过渡期真把 auth 写进过信封(更晚的决定是"桶分只看 mode、不看 auth ⇒ 信封格式一字未改"), 于是那些历史信在我们这儿被判"没进签名域"直接退掉 —— 我们的实现在共享根里真退过三封。 口径:旧信放行(当它是历史),新信照样 fail-closed —— seal() 仍然拒 auth, 而 verify() 放过它(它已无语义 ⇒ 改它也没用);别的未知字段仍然拒。
  • 落盘一律先 .tmp 再 rename(半截文件=一次假死,我们吃过这个亏)。
  • 幂等靠 seen/ 目录本身(状态数组会被截断/丢;只靠数组 ⇒ 重投同一封就会二次交付)。
  • 发号在闸之后(被拒的信不该烧掉一个序号 ⇒ 水位与真实发信量对得上)。
  • 老信没有 mode ⇒ 按"在线"(不把旧信闷死)。

边界(写清楚,别当成保证)

  • 它不认识任何名字 ⇒ provider 撒谎,核心拦不住(这是有意的设计选择 ⇒ 要防就在 provider 那层加校验)。
  • 并发抢号:已测(2026-10-10) —— node scripts/racetest.mjs:12 路真子进程同时 send ⇒ 退出码全 0 + 投出 12 封 + seq 全唯一(1..12);还复现"state 被写倒退"(把 nextSeq 写回 1 ⇒ 新信仍拿新号,因为水位线 state/<as>.seq 兜底)。 负向测试:把认领机制整个短路(纯"读改写")⇒ 24 路跑 3 轮、轮轮撞号(唯一 seq 8/7/8)。 截断路径:已测(2026-10-10 从“未测”改成“已测”)—— seen/ 目录本身 就是永久证据(状态数组会被 slice(-2000) 截断);判据把数组截空、 再把同一封信放回信箱 ⇒ 必须仍判重复;负向证明:只去掉 existsSync 那一路 ⇒ 判据立刻红。
  • 只解决"同一台机器" —— 信箱是盘上的目录;跨机器要靠共享目录。