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-data-contracts

v0.1.0-alpha.9

Published

Shared wire contracts for Tita Data Agent plugins

Readme

dsh-data-contracts

Tita Data Agent 插件之间共享的 wire-level 契约。这里不放领域逻辑,只放形状: 结果信封、跨插件引用与血缘、版本模型、跨插件 HTTP 形状、磁盘布局。

它不是一个界面,也不该有界面:没有 React、没有 CSS、零运行时依赖——Host(Node) 与浏览器半边都要 import 它。

按变更频率分子路径

根入口(dsh-data-contracts)只是全部重导出,留给向后兼容与一次性探查。 正常用法是走子路径,import 语句本身就说明依赖的是哪一类契约:

import { projectRootOf } from 'dsh-data-contracts/layout'

| 子路径 | 装什么 | 变更频率 | | --- | --- | --- | | .../envelope | 结果信封(Envelope / ToolStatus / toolStatus / isRecord) | 最慢:跨所有人的约定 | | .../refs | 跨插件引用与血缘(SheetRef / SelectionRef / LineageContext / LineageRef) | 慢 | | .../version | 版本模型 + 表格地址 + 检查点保留 + 时间线 | 中 | | .../wire | 跨插件 HTTP 形状 + 校验器 | 中:改了两端一起改 | | .../layout | 磁盘布局(projectRootOf / STORAGES_DIRECTORY / isProjectDirectory / 存储文件名规则) | 最慢:持久化契约 |

结果信封(./envelope)

跨工具 / HTTP 边界的返回形状:

| 形状 | 键 | 用在哪 | | --- | --- | --- | | Envelope<T, E> | 5:ok / status / data / warnings / error | 工具与 HTTP 返回 |

  • 构造只走 envelopeSuccess() / envelopeFailure(),不要手写字面量:手写正是 error: null 从 intent 结果里漏掉的原因。
  • status 由 toolStatus(warnings) 派生:warnings 非空即 partial,否则 success; failed 只与 ok: false 同行,所以成功的信封永远不会带它。
  • 错误类型是泛型:知识插件用 Envelope<T, KnowledgeError> 保住 retryable / suggestion,而不必 fork 一份信封。
  • 诚实说明:目前只有 knowledge 插件(/knowledge、/convert、/intent)完整走这个 信封;table 与 notebook 的 HTTP 面还是各自的 { ok, ... } / { ok, error } 形状。 别把这里的类型当成「全产品已经统一」。

跨插件线协议(./wire)

全仓唯一的跨插件 HTTP 调用方向是 notebook → table,两条只读路由:

GET /data-sheet/workbooks.json
GET /data-sheet/workbook/{workbookId}/range?sheet=&session=&range=&styles=1

形状与校验器都定义在这里一次,两端都从它派生:

  • 生产端(table)用契约类型标注返回值,字段改名在它自己那里编译不过;
  • 消费端(notebook)用契约自带的校验器(isWorkbookCatalogResponse / isSheetRangeResponse / isWireErrorBody),形状不对就明确报错,而不是静默降级成 空目录 / 不画样式。

跨插件引用与血缘(./refs)

LineageContext 是跨插件的可选关联字段,推荐由 Host 在一次会话中维护:

sessionId → turnId → intentId → taskId → subtaskId → runId

  • taskId 表示用户的业务目标;同一目标下的多个工作流节点共享它。
  • subtaskId 表示一个可执行的计划节点;组合意图的子节点会记录 parentSubtaskId。
  • runId 表示一次具体执行尝试,重试时应变化。
  • traceId 仅用于技术追踪,不能替代业务 taskId。

ID 由 Host / runtime 生成或继承,模型只提供已有 ID(例如恢复、重试时),不让模型自行编造。 知识卡片用 provenance 记录创建、派生、召回和执行关联;Sheet 操作、Notebook 执行请求、 知识编译报告和召回结果都返回或持久化同一组 lineage 字段。

SheetRef 与 SelectionRef 是 Table / Notebook / 画布之间的最小引用形状。

版本模型(./version)

data-table 与 data-notebook 各保留自己的版本流,只共享语义:

  • ArtifactKind:workbook / notebook。
  • VersionStatus:open / committed / restored / superseded。 committed 取代了历史上的 final(工作表)与「没有状态」(Notebook)。
  • VersionKind:named(手动保存、一次 Agent 调用、一次恢复)与 auto (编辑会话自动产生的检查点)。v3 之前没有 kind 的记录按 named 读。
  • RevisionKind:只有 content 推进内容 revision 并形成 Version; derived / metadata 只进审计流。
  • VersionRecord / VersionStepRecord:版本与版本内的可回放步骤。
  • VersionChange / TextDiffLine:跨插件中立的差异条目。group(工作表 / Notebook Block)用于分组 tab,row / column 让相邻改动能折叠成 hunk,lines 承载文本行级 diff,styleOnly 把「只改了格式」单独归类——否则给一片区域设格式会把 473 个样式单元格 报成 473 处编辑。

版本策略

| 导出 | 规则 | | --- | --- | | VERSION_IDLE_CLOSE_MS = 10 分钟 | 编辑会话的空闲窗口,不是输入防抖 | | VERSION_MAX_STEPS = 20 | 一个 Version 的步数上限,是除空闲窗口外唯一的自动边界 | | AUTO_CHECKPOINT_KEEP = 100 | 保留的最近 auto 检查点数 | | selectAutoCheckpointsToPrune() | 存储裁剪:named 全留、最近 N 条、每天最后一条,并且保护仍然被引用的恢复来源与父版本 | | versionTimeline() | 抽屉列表:收录条件与裁剪规则刻意同形,所以列表不会藏掉存储仍保留的版本,也不会给出已被删掉的版本 | | versionDayLabel() | 今天 / 昨天 / 更早 分组 | | undoTargetOf() | 「撤销这次改动」的目标;优先非 superseded 的前一个版本 | | restoredFromNumberOf() | 把 restoredFromVersionId 解析成用户看到的 V3 |

地址与冲突

  • columnLabel / cellAddress / formatCellReference:唯一的列字母实现 (0 → A、26 → AA、701 → ZZ)。单点与区间分开拼装,所以一个单元格不会写成退化的 C3:C3;引用表格时也不会出现 Sheet1!R3C2 与 R3C2 两套写法。
  • ConflictPayload / conflictPayload():两个插件统一的 409 body (REVISION_CONFLICT + base/current revision + conflictSet + nextAction),让界面能 给出「重新读取 / 查看差异」而不是一句错误字符串。

磁盘布局(./layout)

projectRootOf() / isProjectDirectory() / STORAGES_DIRECTORY:table / notebook / excalidraw 三个插件都必须对「项目根在哪、工作簿和 notebook 存哪」给出同一个答案。这不是 审美问题——notebook 的 Sheet 单元格通过 table 的 HTTP 路由读工作簿,两边算法不一致就是 找不到文件。

存储文件名(storageNameOf / storageSegment / compactDate)

落盘名就是 id,所以「叫什么」也是持久化契约:<project>/storages/<plugin>/<名字>.<扩展名> (工作簿与笔记本 .json,画布 .excalidraw)。新建的文件叫 yyyymmdd-中文名含义,中文名 (日期前缀之后那整段)≤ 25 个字——超过一屏半行的名字等于没有名字。

  • storageNameOf(含义):新建时用。带 yyyymmdd- 前缀的输入保留自己的日期,不叠第二个。
  • storageSegment(文本):只管字符集,保留汉字 / 字母 / 数字 / _ - .,其余并成 -。 已存在的 id 也走它(normalizeWorkbookId / normalizeNotebookId),所以它不含日期、 不截断——给老文件补日期或截短名字,就是让老文件再也找不到。
  • 日期是本地日期,和知识插件 output/<YYYY-MM-DD>/ 按本地日分桶同一条理由:用户说 「今天建的」就是他日历上的今天。

调用方给的 id 原样使用(只过字符集):id 是地址,改一次日期就等于昨天建的文件今天用名字 找不到。

三个插件都要接,但各接各的入口——「新建」与「改一个已存在的」必须分开:

| 插件 | 新建走规则 | 原样使用(地址) | | --- | --- | --- | | Table | workbook_catalog 的 create + name | 给了 workbookId 时 | | Notebook | notebook_catalog 的 create + name | 给了 notebookId 时 | | Excalidraw | excalidraw_create(title)与面板「新建画布」 | 给了 scene / path 时;main.excalidraw 是省略名字时的默认地址,不是新建名 |

画布接得最晚:它长期把新文件也叫 main.excalidraw / 画布 N,于是这条规则对画布的新建 没有生效。两侧共用 scene-names.ts 的 mintedSceneName / uniqueSceneName,不是各写一份。

谁在用

| 消费者 | 用到 | | --- | --- | | Table | ./version ./layout ./wire(生产端) | | Notebook | ./version ./refs ./layout ./wire(消费端) | | Excalidraw | ./layout | | Knowledge | ./envelope ./refs |

源码

发布的包里带 src/。这些函数与形状决定用户看得见的行为(地址怎么写成 Sheet1!C3、 版本时间线怎么分组、diff 往哪个方向渲染),所以改它们属于改界面,不属于重构。

pnpm build
pnpm test