@hasura/metadata-api:基于 OpenAPI 规范构建的 Hasura Metadata API 类型体系
【免费下载链接】graphql-engineBlazing 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 的一切配置(数据源、权限、事件触发器、远程 Schema 等)都通过 Metadata API 来描述,而@hasura/metadata-api正是这一配置面的 TypeScript 类型库:它把引擎对外暴露的 OpenAPI 规范自动生成为类型化的 TS 定义,让开发者可以用强类型的方式导出、导入和操作整个 metadata。本文将围绕该包的核心设计(MetadataV3根类型)、源码生成流水线(OpenAPI → TypeScript → 多语言)、工程配置与自引用类型的补丁修复展开,并结合仓库中的 metadata.openapi.json 与生成脚本,带你理解这套类型体系从规范到产物的完整链路。
Metadata API 类型包要解决什么问题
在 Hasura 中,metadata 是描述引擎配置的“唯一事实来源”,它包含数据源连接信息、表/视图的权限规则、Action、Cron 触发器、Remote Schema 等全部配置。开发者通常通过/v1/metadata端点导出(export)或导入(replace)整个 metadata,而这一 JSON 结构非常庞大且嵌套深。手写这类配置既容易出错,也难以在代码中安全地读写。
@hasura/metadata-api包正是为此而生。根据包的 README 说明,它提供了用于与 Hasura GraphQL Engine Metadata API 协作的 TypeScript 类型,即用于配置 GraphQL Engine 的那套 API 的类型定义。目前该库导出的类型聚焦于整体导出与导入metadata 的场景,根类型为MetadataV3。
在 TypeScript 工程中,只需一行即可引入:
import type { MetadataV3 } from '@hasura/metadata-api'库中提供的其余类型,则是对 metadata 导出中各类属性(如表项、权限、Action 等)的类型别名。这意味着开发者可以基于MetadataV3描述整个配置对象,同时按需引用其中的子类型,实现细粒度的类型安全。
类型的源头:metadata.openapi.json
这套类型并非手工维护,而是从仓库根目录的 metadata.openapi.json 这一 OpenAPI 规范文件自动生成的。该规范文件是 Metadata API 的机器可读描述,定义了各配置对象的字段、类型与嵌套关系,是整个类型生成流水线的唯一输入源。
从仓库的构建配置可以确认这条依赖链:metadata-api-types/Makefile中定义了SCHEMA_FILE := $(abspath ../metadata.openapi.json),并将 TypeScript 源码目录typescript/src声明为依赖该 schema 文件的目标。也就是说,只要规范文件变化,重新执行生成目标即可刷新全部语言类型。
这一设计带来的直接收益是:类型定义与引擎实际 API 始终保持一致,避免了手工维护类型带来的漂移问题;同时一次生成即可覆盖多种语言。
类型生成流水线:从 OpenAPI 到多语言产物
第一步:OpenAPI → TypeScript
首先生成 TypeScript 类型,对应的脚本是 generate-typescript-types.sh。脚本的核心逻辑如下:
npx openapi \ --useUnionTypes \ --input "$SCHEMA_FILE" \ --output src \ --exportServices false \ --exportCore false \ --indent 2这里使用的工具是openapi-typescript-codegen(在package.json的 devDependencies 中声明为^0.23.0),关键参数含义:
--useUnionTypes:对于可空/可选字段生成联合类型(T | null),而不是T | undefined,更贴近 JSON 语义;--exportServices false:不生成 API 调用服务层,本包只关心数据类型;--exportCore false:不生成请求/响应核心工具,保持包轻量;--indent 2:统一 2 空格缩进,保证产物可读性。
生成前脚本会先删除旧的src目录,保证每次生成都是干净的全量重建。
第二步:应用补丁修复生成缺陷
自动生成的代码偶尔需要人工修正,仓库通过git patch 机制解决:生成后脚本遍历patches/*.patch并逐个执行git apply。仓库中现存一个补丁 graphql-value-self-reference-fix.patch,它修复了GraphQLValue_Name这一递归自引用类型的生成问题:
-export type GraphQLValue_Name = (string | null | number | boolean | GraphQLName | Array<GraphQLValue_Name> | Record<string, GraphQLValue_Name>); +export type GraphQLValue_Name = (string | null | number | boolean | GraphQLName | Array<GraphQLValue_Name> | {[property: string]: GraphQLValue_Name});由于 GraphQL 字面量可以无限嵌套(对象值内部再包含对象值),类型生成器对Record<string, ...>的索引签名输出格式与 TypeScript 编译器不完全兼容,补丁将其修正为显式的{[property: string]: ...}索引签名,使递归类型得以正确表达并顺利通过类型检查。这也提醒使用者:升级生成器版本或变更 schema 后,应重新校验补丁是否仍然适用。
第三步:TypeScript → 其他语言
TypeScript 类型生成后,再通过 generate-types-for-lang.sh 借助quicktype将typescript/src/index.ts翻译为其他语言:
quicktype --lang "${LANG}" \ --out "${DIR}/${FILE}" \ --src-lang typescript \ --src "${INDEX_JS}"Makefile中对应的生成目标覆盖 Go、Rust、Haskell、Kotlin 四种语言,分别输出:
go/metadata.openapi.gorust/metadata.openapi.rshaskell/metadata.openapi.hskotlin/metadata.openapi.kt
整体依赖关系在Makefile中清晰可见:Go/Rust/Haskell/Kotlin 四个目标均依赖typescript/src(即必须先完成 TypeScript 生成),而 TypeScript 目标又依赖metadata.openapi.json、package.json、package-lock.json与补丁文件。执行:
make generate-types # 生成全部语言类型 make generate-typescript-types # 仅生成 TypeScript 类型 make typecheck # 对生成的类型做类型检查即可复现完整流水线。
工程配置与发布产物
package.json
package.json 定义了包名为@hasura/metadata-api,当前版本为0.1.0-prerelease.2(预发布阶段,使用时需注意 API 可能演进)。值得注意的配置项:
main与exports均指向./dist/index.js,即发布产物是经tsc编译后的目录;types同样指向./dist/index.js,配合tsconfig.json中开启的declaration: true,编译时同时产出.d.ts声明文件供消费方使用;files字段限定发布内容仅为./dist与./README.md,保证 npm 包体积最小化;- 脚本命令:
build(tsc编译)与typecheck(tsc --noEmit仅校验)。
也就是说,使用方拿到的是一个纯类型 + 编译产物的包,不包含任何运行时 API 客户端。
tsconfig.json
tsconfig.json 继承了@tsconfig/recommended基线,并开启declaration(生成声明文件)、resolveJsonModule(允许导入 JSON,便于在类型层消费 schema 元数据),输出目录为dist,编译入口为src/**/*。由于@tsconfig/recommended默认开启strict等严格选项,生成代码本身也被纳入严格类型检查,这反过来保证了生成脚本产物的质量。
如何在自己的工程中使用
结合包的构建方式与类型设计,典型使用流程如下:
- 本地构建(可选):在 typescript 目录下执行
npm install后运行npm run build,生成dist产物;若只是开发调试,可运行npm run typecheck做纯类型校验。 - 引入根类型:用
import type { MetadataV3 } from '@hasura/metadata-api'获取整个 metadata 导出对象的类型。 - 读取 metadata:将引擎
/v1/metadata导出的 JSON 直接断言为MetadataV3,从而在读写各字段时获得完整补全与编译期校验。 - 操作子类型:针对 metadata 中某个局部片段(如某张表的权限配置),按需引入对应的别名类型,实现局部强类型。
需要说明的是,MetadataV3对应的是 Hasura metadata 的 V3 版本结构,具体字段以当前仓库的 metadata.openapi.json 实际定义为准;该包当前处于预发布版本,接入前建议结合所用引擎版本核对字段兼容性。
仓库内的相关实践与演进脉络
除了metadata-api-types这一套新工具链,仓库中的contrib/metadata-types目录还保留着一套更早的 metadata 类型 SDK 生成方案,可以作为对照参考:
- 它的产出已经预生成在 generated 目录下,包含
HasuraMetadataV2与HasuraMetadataV3两个版本,各自覆盖 Go、Haskell、JSON、Python、TypeScript、YAML 六种格式(如 HasuraMetadataV3.ts); - 其源码侧在 src/types 中维护了手写的
HasuraMetadataV3.ts与 JSON Schema 源,例如PGConfiguration、FromEnv、BigQueryConfiguration、MsSQLConfiguration等类型都带有逐字段的 JSDoc 注释与官方文档链接; - 该目录还提供了基于 JSON Schema 的 IDE 集成方案(VS Code / JetBrains),让编辑器能对
tables.yaml、actions.yaml等 metadata YAML 文件做自动补全与文档提示。
两套方案的对比可以看出演进方向:contrib/metadata-types以手工维护的 TypeScript/JSON Schema 为源,metadata-api-types则直接以引擎的 OpenAPI 规范为唯一真相源并全自动生成,后者在一致性维护上更具优势,且通过 quicktype 打通了 Go/Rust/Haskell/Kotlin 等多语言产物。
小结
围绕 metadata-api-types/typescript/README.md 所描述的@hasura/metadata-api包,本文梳理了其完整技术脉络:以 metadata.openapi.json 为单一事实源,经openapi-typescript-codegen生成 TypeScript 类型,用 git patch 修复GraphQLValue_Name这类递归自引用类型,再经quicktype派生多语言产物,最终以MetadataV3作为整体 metadata 的根类型对外暴露。对于希望安全地程序化读写 Hasura 配置的团队,这套类型体系既降低了手写配置的错误率,也为 CI 中的 metadata 校验与自动化迁移提供了类型层面的保障。
【免费下载链接】graphql-engineBlazing 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),仅供参考