news 2026/9/20 2:58:52

Hasura Data Connector Agents 贡献指南:从 npm 工作区构建、类型生成到发布流程全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hasura Data Connector Agents 贡献指南:从 npm 工作区构建、类型生成到发布流程全解
  • 后端
  • API网关
  • 数据库
  • GraphQL

【免费下载链接】graphql-engine

Blazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.

项目地址:https://gitcode.com/gh_mirrors/gr/graphql-engine
点击查看免费下载

导读

本文面向希望为 Hasura GraphQL Engine 的 Data Connector Agents(数据连接器代理)贡献代码的开发者,系统讲解dc-agents目录下的工程结构、环境搭建、本地启动、API 类型生成与 npm 发布等完整流程。读完本文,你将掌握如何基于 npm workspaces 在dc-api-typesreferencesqlite三个子包之间进行联动开发,如何通过 Makefile 一键派生子锁文件、重新生成 TypeScript 类型,以及理解“开发用 Dockerfile”与“发布用 Dockerfile”的差异与取舍。本文以 dc-agents/CONTRIBUTING.md 为主线,并结合仓库内 Makefile、package.json 及三个 脚本 的源码进行佐证与深化。


一、环境准备与快速上手

1.1 前置要求:Node.js 与 npm

贡献 Data Connector Agents 代码的首要前提是安装 Node.js。仓库在 dc-agents/.nvmrc 中固定了推荐的 Node 版本(当前仓库中该文件内容为v24.28.0),因此最稳妥的做法是使用 nvm 这类 Node 版本管理器:

  1. 安装 nvm;
  2. 进入dc-agents目录后执行nvm use,让其依据.nvmrc自动切换到正确版本;
  3. 执行npm ci恢复全部 npm 依赖。

注意:原版 CONTRIBUTING.md 撰写时建议的版本是 NodeJS 16,而当前仓库的.nvmrc与 reference/Dockerfile(基于node:24-alpine)已经跟进到 Node 24,实际开发时以.nvmrc与各包package.json中声明的依赖为准。npm cinpm install的区别在于前者严格按锁文件安装、保证可复现,因此文档推荐使用npm ci

1.2 一次性还原所有依赖

所有子项目共享一份依赖,只需在dc-agents根目录(即本目录)执行一次还原:

npm ci

根目录的 package.json 通过workspaces字段声明了三个工作区成员:

{ "name": "@hasura/dc-agents", "private": true, "workspaces": ["dc-api-types", "reference", "sqlite"] }

这意味着dc-api-typessqlitereference都是被根工作区纳入管理的 npm 包,安装完成后它们会以符号链接(symlink)的形式出现在根node_modules中。


二、项目结构:五个组成部分

dc-agents目录下包含以下核心模块:

目录作用
dc-api-typesData Connector Agent API 的 TypeScript 类型,由 OpenAPI 规范生成;而 OpenAPI 规范又来源于server/lib/dc-api/src中的 Haskell 类型
referenceReference Agent,作为 Data Connector Agent 的示例实现
sqliteSQLite Data Connector Agent,对接 SQLite 数据库的真实代理
sdk打包进 Data Connector SDK zip 文件中的资产
scripts用于管理代码库的各类脚本

关于dc-api-types的定位,可以在其 package.json 中看到关键声明:

{ "name": "@hasura/dc-api-types", "version": "0.46.0", "types": "./src/index.ts", "exports": "./src/index.ts" }

它直接导出src目录下的 TypeScript 源码作为类型入口,这也是它能被referencesqlite两个包即时消费的基础。

2.1 工作区联动:改一处,处处生效

dc-api-typessqlitereference都是被根工作区纳入管理的 npm 包。借助 npm workspaces 的符号链接机制,当你修改dc-api-types时,这些改动会立刻流入referencesqlite,无需重新发布或手动复制文件。

这一机制在 scripts/derive-lockfile.ts 的头部注释中有非常清晰的说明:

由于 workspaces 会在根工作区的node_modules中通过符号链接指向磁盘上真实的包目录,因此对工作区包的任何修改都会透明地反映到依赖它的包中。例如reference依赖dc-api-types,npm 会创建从./node_modules/@hasura/dc-api-types./dc-api-types的符号链接,reference读取该路径时实际看到的就是dc-api-types的源码。

从依赖声明上也能印证这一点:reference/package.json 与 sqlite/package.json 都依赖"@hasura/dc-api-types": "0.46.0"


三、派生子锁文件(Deriving Lockfiles)

3.1 为什么要派生子锁文件

由于sqlitereference被链接进根工作区,npm 通常不会为它们各自生成package-lock.json,锁文件统一由根工作区管理(即dc-agents/package-lock.json)。但社区存在一个现实需求:把这些项目脱离当前工作区环境独立构建,例如:

  • 单独拷贝进 Docker 容器运行;
  • 通过 copybara 等工具导出到其他仓库。

此时根package-lock.json不存在,子项目必须自带锁文件才能执行npm ci

为此,仓库提供了一套工具,能够从根锁文件派生出referencesqlite各自的package-lock.json。这些派生的锁文件被提交到仓库中,供包专用的 Dockerfile(如 reference/Dockerfile)在容器内独立还原依赖时使用。

3.2 何时需要重新派生

只要修改了根package-lock.json(而它会在你改动任一包依赖时发生变化),就必须重新派生各子包的锁文件。执行方式非常简单:

make derive-lockfiles

该命令对应 Makefile 中的目标:

derive-lockfiles: npm run derive-lockfiles

而根 package.json 中的脚本则实际调用:

"derive-lockfiles": "ts-node ./scripts/derive-lockfile.ts --lockfile package-lock.json --workspace reference --workspace sqlite"

3.3 派生算法背后的三个细节

关于派生过程,scripts/derive-lockfile.ts 源码揭示了三处值得注意的细节:

  1. 解析符号链接条目:npm 根锁文件在 symlink 一个包(如node_modules/@hasura/dc-api-types)时会有特殊条目,派生时必须用被链接包的真实信息替换这些条目,因为派生的锁文件要脱离工作区独立使用,容器里不存在符号链接。
  2. 共享依赖留在根层:npm 会把工作区之间共享的、或根包使用的依赖“上浮”到根node_modules。当派生目标包恰好用到这些依赖时,保持其在根层即可(源路径与目标路径一致)。
  3. 版本冲突下压:同一包名可能存在多个版本。例如根层已有camelcasev6(由openapi-typescript-codegen引入),而reference的某传递依赖(如args)需要 v5,npm 会把 v5 安装在reference/node_modules/camelcase。若直接上浮到根层会覆盖 v6,因此派生时会把 v5下压到依赖它的包的node_modules下(node_modules/args/node_modules/camelcase),让两个版本共存。

派生的锁文件固定使用lockfileVersion: 3——这是与 npm 当前默认 v2 相同、但去掉了仅为旧版 npm 保留的向后兼容dependencies属性的版本,这样派生脚本也无需重写该属性。


四、两套 Dockerfile:开发与发布的分工

每个 agent 实际上都有两份 Dockerfile,以 Reference Agent 为例:

  • dc-agents/Dockerfile-reference
  • dc-agents/reference/Dockerfile

(SQLite Agent 同样如此,见dc-agents/Dockerfile-sqlite。)

4.1 开发用:Dockerfile-reference

这份 Dockerfile 构建出的容器会复制整个根工作区进入容器,并在容器内维持工作区结构。从实际文件内容看(dc-agents/Dockerfile-reference):

COPY package.json . COPY package-lock.json . COPY dc-api-types dc-api-types WORKDIR /app/reference COPY ./reference/package.json . RUN npm ci

它同时带入了dc-api-typesreference。这样,即使dc-api-types中有尚未发布到 npm 的改动,容器内也能通过工作区结构直接引用到最新类型代码。适合开发阶段反复验证

4.2 发布用:reference/Dockerfile

另一份 reference/Dockerfile 则脱离工作区独立构建

FROM node:24-alpine WORKDIR /app COPY package.json . COPY package-lock.json . RUN npm ci COPY tsconfig.json . COPY src src RUN npm run typecheck EXPOSE 8100 CMD [ "npm", "run", "--silent", "start-no-typecheck" ]

它只复制reference自身的package.json与派生出的package-lock.json,并尝试从 npm 还原@hasura/dc-api-types适合官方发布——此时所有依赖都已发布到 npm、可以被正常还原。

另外两份 Dockerfile 都体现了两个工程实践:

  • 构建阶段先跑一次npm run typecheck,保证镜像内代码可编译;
  • 运行时使用start-no-typecheck(仅做 TS→JS 转译)以降低运行时内存占用。

五、本地启动两个 Agent

开始前请确保已执行过npm ci

5.1 启动 Reference Agent

make start-reference-agent

5.2 启动 SQLite Agent

make start-sqlite-agent

这两个目标在 Makefile 中的定义非常直白,本质是启动对应工作区包的 npm 脚本:

start-reference-agent: npm start -w reference start-sqlite-agent: npm start -w sqlite

其中npm start -w <workspace>是 npm workspaces 的标准用法,等价于进入对应子包执行其start脚本。两个子包的package.jsonstart均为ts-node ./src/index.ts,即直接用 ts-node 运行 TypeScript 源码,便于开发调试。Reference Agent 容器内默认监听 8100 端口(见 Dockerfile 中的EXPOSE 8100)。


六、生成 TypeScript 类型(dc-api-types

6.1 完整再生成:make regenerate-types

当 Haskell 侧类型变更后,需要把变更传导到 TypeScript 类型。执行:

make regenerate-types

该命令会依次完成(见 Makefile 与 scripts/generate-types.sh):

  1. 删除dc-api-types/src/agent.openapi.json(即旧 OpenAPI 规范);
  2. 调用generate-types.sh重新生成;
  3. 提升dc-api-types项目的版本号;
  4. 更新referencesqlite两个 agent 对@hasura/dc-api-types的版本依赖;
  5. 更新并重新派生全部锁文件。

6.2 仅从 OpenAPI 规范生成:make generate-types

如果 OpenAPI 规范(dc-api-types/src/agent.openapi.json)已存在、只想重新生成 TypeScript 类型本身,可执行:

make generate-types

scripts/generate-types.sh 展示了这条生成链路的完整细节:

if [ ! -f $SCHEMA_FILE ] ; then # agent.openapi.json 不存在时,通过 Haskell 测试套件导出 $TESTS_DC_API export-openapi-spec | tail -n 1 | jq . > $SCHEMA_FILE fi # 删除旧模型并重新生成 rm -rf "$TYPES_DIR/models" rm -f "$TYPES_DIR/index.ts" npx openapi --useUnionTypes --input "$SCHEMA_FILE" --output "$TYPES_DIR" \ --exportServices false --exportCore false --indent 2

关键点:

  • OpenAPI 规范的来源是 Haskellagent.openapi.json由 Data Connector API 的 Haskell 测试可执行文件通过export-openapi-spec子命令导出(对应 Makefile 中的TESTS_DC_API := cabal run dc-api:test:tests-dc-api --,Haskell 类型定义位于 server/lib/dc-api/src)。
  • 类型生成工具是openapi-typescript-codegen(根 package.json 的 devDependencies 中声明了"openapi-typescript-codegen": "^0.31.0"),生成时使用--useUnionTypes、且不导出 services 与 core,只保留纯数据类型。
  • 版本号自动提升:脚本会检查dc-api-types/package.json中版本号是否已有改动,若没有则执行npm version minor(提升 minor 版本),随后调用update-api-types-deps.sh同步所有下游依赖;若已改动则跳过,避免重复升级。

6.3 手动调整版本号:make update-api-types-deps

如果你需要手动修改dc-api-types项目中的版本号,可执行:

make update-api-types-deps

对应的 scripts/update-api-types-deps.sh 会读取dc-api-types/package.json中的最新版本,用jqreferencesqlite两个项目dependencies中的@hasura/dc-api-types依赖更新到该版本,然后依次执行npm installmake derive-lockfiles,保证锁文件与新版本号保持同步:

TYPES_VERSION=$( jq '.version' "$TYPES_PROJECT_DIR/package.json" ) for project in "${PROJECT_DIR_NAMES[@]}"; do jq ".dependencies[\"@hasura/dc-api-types\"] = $TYPES_VERSION" \ "$PROJECT_DIR/package.json" > "$TMP_FILE" mv -f "$TMP_FILE" "$PROJECT_DIR/package.json" done npm install make derive-lockfiles

6.4 日常质量保障:typecheck 系列目标

除上述生成类目标外,Makefile 还提供了类型检查系列目标,适合在改动后快速验证:

typecheck: typecheck-dc-api-types typecheck-reference-agent typecheck-sqlite-agent

分别对dc-api-typesreferencesqlite执行tsc --noEmit,可在本地 CI 之前拦截类型错误。


七、发布dc-api-types到 npm

TypeScript 类型包dc-api-types会由持续集成(CI)构建系统在每次向 main 分支提交时自动发布到 npm

发布有一个幂等保护机制:仅当dc-api-types/package.json中指定的版本尚未被发布过时,才会真正发布该版本。这意味着:

  • 正常开发流程中无需手动发布,CI 会自动完成;
  • 重复提交不会产生同版本覆盖,避免破坏已引用旧版本的消费者;
  • 若某次提交没有改变类型(版本号未提升),CI 检测到版本已存在便会跳过发布。

这也解释了为什么make regenerate-types会主动执行npm version minor——确保类型有改动时版本号一定变化,从而触发一次有效的自动发布。


八、常见问题与最佳实践小结

场景推荐操作依据
首次克隆仓库后还原依赖dc-agents目录执行npm ciCONTRIBUTING.md
修改了任一包的依赖重新运行make derive-lockfiles并提交派生的子锁文件Makefile、derive-lockfile.ts
Haskell 侧类型变更后同步到 TS执行make regenerate-typesgenerate-types.sh
仅重新生成 TS 类型(规范已存在)执行make generate-typesgenerate-types.sh
手动改了dc-api-types版本号执行make update-api-types-deps同步下游依赖update-api-types-deps.sh
开发中需要验证未发布类型使用Dockerfile-reference(含工作区)dc-agents/Dockerfile-reference
官方发布使用reference/Dockerfile(独立还原 npm 依赖)reference/Dockerfile

综合来看,Data Connector Agents 的工程体系围绕一条核心链路运转:Haskell 类型(server/lib/dc-api/src)→ OpenAPI 规范(dc-api-types/src/agent.openapi.json)→ TypeScript 类型(dc-api-types)→ 各 agent 消费(reference/sqlite)→ npm 自动发布。贡献者只要掌握npm ci、三个 Makefile 目标(derive-lockfilesregenerate-typesupdate-api-types-deps)以及两套 Dockerfile 的取舍,即可顺畅地参与该模块的开发与发布全流程。

  • 后端
  • API网关
  • 数据库
  • GraphQL

【免费下载链接】graphql-engine

Blazing fast, instant realtime GraphQL APIs on all your data with fine grained access control, also trigger webhooks on database events.

项目地址:https://gitcode.com/gh_mirrors/gr/graphql-engine
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 2:57:42

WSL2搭建Geant4和ROOT完整指南:从零开始打造粒子物理仿真环境

最近帮实验室一个小师弟在一台满是Windows软件的工作机上搭研究环境&#xff0c;又是跑Geant4又是跑ROOT&#xff0c;折腾了两天总算把整套链路全部打通。这事本身不算难&#xff0c;但踩坑的地方是真的多——WSL2底下的Ubuntu子系统&#xff0c;稍不留神就会在编译阶段卡死一下…

作者头像 李华
网站建设 2026/9/20 2:55:34

方案 A vs 方案 B

方案 A vs 方案 B 【免费下载链接】baoyu-skills 项目地址: https://gitcode.com/gh_mirrors/ba/baoyu-skills Overview 一张对比两种技术选型优劣势的双栏信息图。 Learning Objectives 观众将理解&#xff1a;两方案的核心差异、各自适用场景、最终推荐。 Section…

作者头像 李华
网站建设 2026/9/20 2:54:21

多代理编排实战:5分钟让一句提问被路由到最合适的AI代理

多代理编排实战&#xff1a;5分钟让一句提问被路由到最合适的AI代理 【免费下载链接】agent-squad Flexible and powerful framework for managing multiple AI agents and handling complex conversations 项目地址: https://gitcode.com/GitHub_Trending/mu/agent-squad …

作者头像 李华
网站建设 2026/9/20 2:50:46

C盘爆红不用怕:4个文件夹清理法,10分钟释放100G

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华