Himalaya pimdir 后端重构:让邮箱名等于集合 ID(collection id),移除 `pimdir.namespace` CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载本文基于 Himalaya 仓库中已落地的变更方案 proposal.md 与其配套的 delta.md 编写讲解 pimdir 后端一次面向形状正确性shape correctness的重构pimdir 邮箱不再由集合 ID 剥去namespace/前缀派生而来而是原样等于集合 ID 本身。读完本文你将理解 pimdir 存储对集合 ID 的语义约束opaque id、Himalaya 为何以及如何移除pimdir.namespace配置项、Mailbox.name如何改为直接承载集合行的自身名称以及mailbox.alias机制如何成为用户免输入长 ID 的唯一官方途径。背景pimdir 后端与集合 ID 的存储模型Himalaya 的 pimdir 后端是一层本地离线缓存适配器它把io-pimdir提供的存储一个 SQLite 索引pimdir.db加内容寻址的 blob 对象目录objects/包装成共享的跨协议客户端接口。该存储由 Neverest 同步引擎写入Himalaya 只以 reader 角色读取、以 producer 角色暂存写操作从不直接修改索引见 src/pimdir/backend.rs 模块文档与 src/pimdir/client.rs 的PRODUCER常量与new()实现。在这个模型里一封邮件、一个文件夹都落在collection集合上而每个集合有一个collections.id。关键约束来自 pimdir 规范第 9.2 节Himalaya 源码与变更文档反复引用collections.id是不透明的TEXT PRIMARY KEY存储store既不解析也不校验该 ID不会按任何分隔符把它切开层级关系通过parent外键建模而不是通过 ID 里的路径分隔符。换句话说imap/INBOX里的/对存储而言没有任何结构意义它只是 Neverest 同步引擎producer自己的命名约定。规范层面这套命名被明确标记为“内部约定、不可配置”internal and non-configurable。问题剥前缀命名是“错误形状”在本次变更之前pimdir 后端给邮箱命名的方式是把集合 ID 剥掉namespace/前缀。于是imap/INBOX这个集合在 Himalaya 里显示、解析为INBOX。提案文档 proposal.md 直白地把它定性为“读起来顺眼但形状是错的”混合了两种不同职责寻址键addressing key与展示便利display convenience被揉在一个名字里一个邮箱响应两种拼写imap/INBOX和INBOX都能命中同一个集合命令语义出现歧义前缀不是存储能回答的问题存储对 ID 不解析、不校验前缀只存在于 producer 的约定里因此剥前缀本质上是对约定的一次“猜测”guess而不是查询猜测带来连锁复杂度为让派生逻辑在“无法决定”的情况下还能工作不得不引入pimdir.namespace配置项还需要一个 kind媒体类型过滤器才能让派生成立并且要处理“两个集合剥壳后撞成同一个名字”的歧义错误路径。提案明确指出这种派生把“展示便利”内置进了寻址路径而正确的做法是把两者彻底分开。变更核心集合 ID 即邮箱原样呈现变更后的规则只有一句话pimdir 邮箱就是它的集合 ID逐字原样verbatim。具体落点见 delta.md 的 ADDED Requirements-m imap/INBOX是唯一合法拼写imap/INBOX也是列表输出两列ID 与 name里的内容不再派生、不再剥壳、不接受任何缩短拼写没有任何配置项可以提供一个缩短拼写——pimdir.namespace被整体移除。这与 JMAP 后端早已采用的形状完全一致JMAP 邮箱 ID 是不透明的服务端字符串Mailbox.id原样携带而[mailbox.alias]配置正是为“用户不想打长 ID”这个需求而存在的官方机制无需为 pimdir 发明新概念。代码级验证hub_id只剩存在性检查在 src/pimdir/backend.rs 中hub_id是这次变更保留下来的关键函数。它的当前实现只做一件事把用户输入的邮箱名拿去向账户的邮件集合 ID 集合做存在性核对命中了原样返回没命中就拒绝并列出存储里实际持有的集合pub(crate) fn hub_id(self, mailbox: str) - ResultString { let mut ids: VecString self .mail_collections()? .into_iter() .map(|collection| collection.id) .collect(); if ids.iter().any(|id| id mailbox) { return Ok(mailbox.to_string()); } ids.sort(); bail!( Mailbox {mailbox} not found in the pimdir store, which holds: {}, ids.join(, ), ) }方法注释解释了“为什么保留这道核对而不是直接透传”一个没有任何写入的 ID 如果被直接透传给存储会被读成“存在但为空”的邮箱掩盖拼写错误。所以“匹配不到集合就拒绝”是hub_id旧实现里值得保留的那一半——mail_collections()还会先用is_mail()媒体类型message/rfc822匹配见同一文件的MAIL_KIND常量过滤出真正的邮件集合。列表输出Mailbox.name承载集合行自身的名字在 src/pimdir/backend.rs 的list_mailboxes中每个Mailbox的构造现在是mailboxes.push(Mailbox { id: collection.id, name: collection.name, role: None, total, unread: None, });Mailbox.name直接携带集合行的name字段而不是派生值——这正是 JMAP 的形状。当前 Neverest 会把该列初始化为与 ID 相同的内容所以两个列都显示imap/INBOX将来某个存储若在collections.name里写入更短的展示名Himalaya 无需任何改动就会把它展示出来。配置变化PimdirConfig只保留root与accountpimdir.namespace的移除在配置结构体层面有直接证据。当前 src/config.rs 中的PimdirConfig只剩两个字段#[serde(rename_all kebab-case, deny_unknown_fields)] pub struct PimdirConfig { /// The store directory, holding pimdir.db and objects/. #[serde(deserialize_with shell_expanded_path)] pub root: PathBuf, /// The sync engine account whose collections this client reads, /// per pimdir SPEC section 9.2. #[serde(default)] pub account: OptionString, }rootNeverest 写入的存储目录含pimdir.db与objects/支持~展开shell_expanded_pathaccount通常留空——单账户同步的存储会被直接读为那个账户多个账户共用的存储必须显式指定猜错了会显示错误的邮箱集合见 src/pimdir/client.rs 的resolve_account。对应的示例配置在 config.sample.toml 的 pimdir 段落里已经更新并明确写入了新规则与别名建议# A mailbox is its collection id, verbatim: Neverest binds a sources # collections under a namespace, so the mailbox your server calls INBOX is # imap/INBOX here and that is what -m takes. Alias it to avoid typing it: #mailbox.alias.inbox imap/INBOX注意deny_unknown_fields旧配置里残留的pimdir.namespace现在会直接触发反序列化错误而不是被静默忽略——这是破坏性变更breaking change的一种显式体现。实战行为两个可验证场景delta.md 用两个场景scenario固化了新行为可以直接作为验收标准场景一邮箱用集合 ID 寻址前提一个 pimdir 账户其存储的邮件集合键形如imap/name动作列出邮箱并以imap/INBOX寻址结果两列都显示imap/INBOX命令命中该集合。场景二缩短名不是邮箱前提同一账户动作命令寻址INBOX结果被拒绝并报出账户实际持有的集合名错误信息来自上面hub_id的bail!。仓库内的单元测试同样以imap/Sent这类完整集合 ID 为基准例如 src/pimdir/backend.rs 的sent_store()用store.ensure_collection(imap/Sent, MAIL_KIND)建集合后续send_message(Some(imap/Sent), ...)、pending_actions(imap/Sent)都以完整 ID 寻址——任何剥前缀逻辑在测试层面都不复存在。JMAP 先例与mailbox.alias免输入长 ID 的官方通道提案选择“不发明新概念”的理由之一是Himalaya 已经有一个“ID 不是名字”的后端——JMAP。JMAP 的Mailbox.id是服务器返回的不透明字符串用户通过[mailbox.alias]避免手工输入。pimdir 完全复用这套机制mailbox.alias.inbox imap/INBOX让-m INBOX或省略-m时默认的收件箱仍然好用别名的语义在 src/email/mailbox.rs 的Mailbox结构体中体现role字段来自后端报告或mailbox.alias.role条目通用别名规则在 config.sample.toml 有完整示例inbox/sent/drafts/trash/junk 等。这也解释了为何“Not in scope”不在本次范围内Neverest 是否应该在collections.name里写一个短展示名属于 producer 侧的问题本次变更对两种答案都保持兼容。变更记录与迁移提示本次变更是破坏性的CHANGELOG 已明确标注见 CHANGELOG.mdBREAKING: apimdirmailbox is now its collection id, verbatim, andpimdir.namespaceis removed.对现有用户的迁移提示删除配置中的pimdir.namespace键否则deny_unknown_fields会拒绝解析把脚本、别名里所有-m INBOX之类的缩短拼写改为完整集合 ID如-m imap/INBOX不想每次打全名就为角色配置mailbox.alias例如mailbox.alias.inbox imap/INBOX。整个变更的落地轨迹记录在 变更日志 与 任务清单移除 namespace、移除派生与客户端字段、Mailbox.name直载集合行名、hub_id收窄为存在性检查、真实存储验证、更新规格与 CHANGELOG 均已勾选完成。小结这次重构的实质是把寻址键与展示便利彻底解耦集合 ID 对存储是不透明的任何剥前缀行为都只是对 producer 约定的猜测猜就需要配置项和歧义处理来兜底。让“pimdir 邮箱 集合 ID逐字原样”之后寻址路径变直、配置面收窄PimdirConfig只剩root与account、与 JMAP 的形状统一而“打全名太累”这个真实需求则交给早已存在的mailbox.alias机制——不多造一个概念也不多留一条歧义路径。赞分享CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载相关推荐Himalaya pimdir 后端重构邮箱即集合 IDA pimdir mailbox is its collection id完整解析Himalaya pimdir 后端重构邮箱即集合 IDA pimdir mailbox is its collection id完整解析 本文围绕 HiCLIHimalaya pimdir 后端邮箱即集合 IDCollection ID——一次移除命名空间剥离的破坏性变更详解Himalaya pimdir 后端邮箱即集合 IDCollection ID——一次移除命名空间剥离的破坏性变更详解 本文以 Himalaya 仓库中CLIhimalaya pimdir 后端公开 ID 实现详解用短 seq 取代内部 link_idhimalaya pimdir 后端公开 ID 实现详解用短 seq 取代内部 link_id 本文围绕 himalaya 仓库中已落地的 Cairn 变更提CLI上一篇Cube Vertica 数据库驱动为 Cube 语义层接入 Vertica 的配置、原理与源码解析下一篇在 Snowpack 中集成 PostCSSsnowpack/plugin-postcss 完整使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考