- 后端
- 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.
导读
本文面向希望为 Hasura GraphQL Engine 的 Data Connector Agents(数据连接器代理)贡献代码的开发者,系统讲解dc-agents目录下的工程结构、环境搭建、本地启动、API 类型生成与 npm 发布等完整流程。读完本文,你将掌握如何基于 npm workspaces 在dc-api-types、reference、sqlite三个子包之间进行联动开发,如何通过 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 版本管理器:
- 安装 nvm;
- 进入
dc-agents目录后执行nvm use,让其依据.nvmrc自动切换到正确版本; - 执行
npm ci恢复全部 npm 依赖。
注意:原版 CONTRIBUTING.md 撰写时建议的版本是 NodeJS 16,而当前仓库的
.nvmrc与 reference/Dockerfile(基于node:24-alpine)已经跟进到 Node 24,实际开发时以.nvmrc与各包package.json中声明的依赖为准。npm ci与npm 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-types、sqlite、reference都是被根工作区纳入管理的 npm 包,安装完成后它们会以符号链接(symlink)的形式出现在根node_modules中。
二、项目结构:五个组成部分
dc-agents目录下包含以下核心模块:
| 目录 | 作用 |
|---|---|
dc-api-types | Data Connector Agent API 的 TypeScript 类型,由 OpenAPI 规范生成;而 OpenAPI 规范又来源于server/lib/dc-api/src中的 Haskell 类型 |
reference | Reference Agent,作为 Data Connector Agent 的示例实现 |
sqlite | SQLite 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 源码作为类型入口,这也是它能被reference、sqlite两个包即时消费的基础。
2.1 工作区联动:改一处,处处生效
dc-api-types、sqlite和reference都是被根工作区纳入管理的 npm 包。借助 npm workspaces 的符号链接机制,当你修改dc-api-types时,这些改动会立刻流入reference和sqlite,无需重新发布或手动复制文件。
这一机制在 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 为什么要派生子锁文件
由于sqlite和reference被链接进根工作区,npm 通常不会为它们各自生成package-lock.json,锁文件统一由根工作区管理(即dc-agents/package-lock.json)。但社区存在一个现实需求:把这些项目脱离当前工作区环境独立构建,例如:
- 单独拷贝进 Docker 容器运行;
- 通过 copybara 等工具导出到其他仓库。
此时根package-lock.json不存在,子项目必须自带锁文件才能执行npm ci。
为此,仓库提供了一套工具,能够从根锁文件派生出reference与sqlite各自的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 源码揭示了三处值得注意的细节:
- 解析符号链接条目:npm 根锁文件在 symlink 一个包(如
node_modules/@hasura/dc-api-types)时会有特殊条目,派生时必须用被链接包的真实信息替换这些条目,因为派生的锁文件要脱离工作区独立使用,容器里不存在符号链接。 - 共享依赖留在根层:npm 会把工作区之间共享的、或根包使用的依赖“上浮”到根
node_modules。当派生目标包恰好用到这些依赖时,保持其在根层即可(源路径与目标路径一致)。 - 版本冲突下压:同一包名可能存在多个版本。例如根层已有
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-referencedc-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-types与reference。这样,即使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-agent5.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.json中start均为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):
- 删除
dc-api-types/src/agent.openapi.json(即旧 OpenAPI 规范); - 调用
generate-types.sh重新生成; - 提升
dc-api-types项目的版本号; - 更新
reference、sqlite两个 agent 对@hasura/dc-api-types的版本依赖; - 更新并重新派生全部锁文件。
6.2 仅从 OpenAPI 规范生成:make generate-types
如果 OpenAPI 规范(dc-api-types/src/agent.openapi.json)已存在、只想重新生成 TypeScript 类型本身,可执行:
make generate-typesscripts/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 规范的来源是 Haskell:
agent.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中的最新版本,用jq将reference与sqlite两个项目dependencies中的@hasura/dc-api-types依赖更新到该版本,然后依次执行npm install与make 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-lockfiles6.4 日常质量保障:typecheck 系列目标
除上述生成类目标外,Makefile 还提供了类型检查系列目标,适合在改动后快速验证:
typecheck: typecheck-dc-api-types typecheck-reference-agent typecheck-sqlite-agent分别对dc-api-types、reference、sqlite执行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 ci | CONTRIBUTING.md |
| 修改了任一包的依赖 | 重新运行make derive-lockfiles并提交派生的子锁文件 | Makefile、derive-lockfile.ts |
| Haskell 侧类型变更后同步到 TS | 执行make regenerate-types | generate-types.sh |
| 仅重新生成 TS 类型(规范已存在) | 执行make generate-types | generate-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-lockfiles、regenerate-types、update-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.
相关推荐
LogicFlow 代码贡献指南:从 Issue 提交、PR 协作到 npm 发布全流程解析
LogicFlow 代码贡献指南:从 Issue 提交、PR 协作到 npm 发布全流程解析 导读 本文以 LogicFlow 仓库的 贡献规范文档 https
前端低代码流程编排Feast Web UI 贡献开发指南:从 `feast ui` 到 NPM 发布全流程
Feast Web UI 贡献开发指南:从 feast ui 到 NPM 发布全流程 Feast Web UI 是 Feast Feature Store 的前
MLOps后端数据工程Bruno 本地开发环境搭建与 npm 多工作区构建流程详解(贡献者指南)
Bruno 本地开发环境搭建与 npm 多工作区构建流程详解(贡献者指南) 本文基于 Bruno 仓库的贡献者文档( docs/contributing/con
开发工具接口测试桌面应用CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考