
Mastra 类型构建器 internal/types-builder跨包捆绑类型的内联、校验与结构性边界规则【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra本篇技术指南以 Mastra 仓库中的内部工具包 packages/_types-builder/AGENTS.md 为骨架深入讲解internal/types-builder如何在发布.d.ts声明文件时内联internal/*依赖类型、校验声明文件的运行时依赖合法性并解释其核心设计约束——被捆绑的类型必须是结构性的structural。读完本文你将掌握generateTypes()的完整流水线、dist/_types/内联机制、IMastraAuthProvider结构接口的由来以及 #18682 这类跨包名义类型nominal typing陷阱的规避方法。一、工具定位解决 monorepo 内部类型依赖的发布难题Mastra 是一个基于 pnpm workspace 的 TypeScript monorepopackages/core等公共包会通过deps.alwaysBundle见 packages/core/tsdown.config.ts将internal/*系列内部包直接打进产物。这带来一个连锁问题运行时依赖被捆绑后声明文件.d.ts中的import ... from internal/...在发布后就无法解析了。internal/types-builder正是为处理这一场景而生的构建期工具。按 packages/_types-builder/AGENTS.md 的定位描述它负责为捆绑了internal/*及其他类型依赖的包生成可发布的.d.ts产物通过generateTypes()编译声明将捆绑的包类型内联进dist/_types/目录校验声明文件只引用运行时依赖或已捆绑的包。工具本身是一个private: true的内部包package.json依赖microsoft/api-extractor声明折叠/回滚、ts-morphAST 改写、tinyglobby文件扫描、local-pkg与resolve.exports包解析、typescript并通过 exports 暴露./embed-types与./compile-zod两个子入口。二、generateTypes()一次发布类型构建的完整流水线generateTypes(rootDir, bundledPackages)是工具的主入口src/index.js接受两个参数参数类型含义rootDirstring目标包的根目录如process.cwd()bundledPackagesSetstring被捆绑进产物的包名集合支持ai-sdk/*这样的通配符前缀整个流程分四步每一步都有对应的源码实现2.1 调用 tsc 编译声明const tscProcess spawn(npx, [tsc, -p, tsconfig.build.json], { cwd: rootDir, stdio: inherit, shell: true, env: getFilteredEnv(), });这里刻意使用spawnstdio: inherit而非exec让 TypeScript 的编译错误直接透传到终端并使用shell: true保证跨平台兼容。值得注意的细节是getFilteredEnv()src/index.js它会剔除一批 pnpm 特有的环境变量如npm_config_catalog、pnpm_config_patched-dependencies等避免这些变量被透传给npx/npm时产生 Unknown env config 警告。2.2 内联捆绑类型到 dist/_types/编译完成后工具用 tinyglobby 扫描dist/**/*.d.ts对每个声明文件调用replaceTypes(fullPath, rootDir, bundledPackages)src/index.js其核心逻辑在 src/replace-types.js用 ts-morph 解析声明文件的 import/export/import()语句找出所有匹配bundledPackages的模块说明符通过local-pkg的getPackageInforesolve.exportsconditions: [types]解析出源包的声明入口若解析不到则回退尝试types/*包调用copyDeclarationGraph将源包的声明文件连同其相对依赖的整个声明图递归复制到dist/_types/pkg名/ 替换为 _/下把原声明文件中对捆绑包的 import 改写为指向dist/_types/的相对路径。copyDeclarationGraph还会用visitedSet 做去重避免同一个声明文件被反复复制。另一个跨平台细节是路径分隔符path.relative()在 Windows 上会返回反斜杠而.d.ts中的模块说明符必须使用 POSIX 分隔符/否则moduleResolution: bundler下会解析失败——因此代码中显式执行了.split(sep).join(/)src/replace-types.js。2.3 重写相对导入以兼容 ESM对于每个.d.ts工具还会用正则重写形如from ./foo的相对导入src/index.js若目标是一个目录则追加/index.js否则追加.js后缀已经以.js结尾或本身就是.d.ts/.d.mts/.d.cts的导入保持不变。这一步骤让生成的声明文件在 NodeNext / ESM 环境下也能正确解析。2.4 校验声明文件的运行时依赖validateDeclarationRuntimeImports(rootDir, bundledPackages)src/index.js是最后一道质量闸门。它读取目标包的package.json收集dependencies、peerDependencies、optionalDependencies与bundleDependencies/bundledDependencies作为合法运行时依赖集合然后扫描dist/**/*.d.ts跳过_types/目录中的所有导入说明符判断其包名是否等于当前包自身或在运行时依赖集合中含types/*变体或匹配bundledPackages中的某项支持pkg/*通配。任何不满足条件的导入都会被标记为devDependency或undeclared dependency最终抛出带完整清单的错误Generated declaration files reference packages that are not runtime dependencies. Add the package to generateTypes(..., bundledPackages), move it to dependencies/peerDependencies, or remove it from the public types.这条错误信息本身就是一套明确的修复指引把包加入bundledPackages、移到dependencies/peerDependencies或从公开类型中移除。三、边界规则被捆绑的类型必须保持结构性这是 AGENTS.md 中最核心、也最具工程价值的部分。背景是捆绑的声明文件是副本——多个已发布包各自携带同一份internal/*声明的拷贝例如mastra/core和每个 auth 提供方各自内嵌了MastraAuthProvider/MastraBase。TypeScript 对#private与protected成员采用名义性nominal检查两个结构完全相同、但各自带#private字段的类副本彼此之间是不可赋值的。这直接导致了用户侧的编译失败——server.auth new MastraAuthWorkos()报错issue #18682因为MastraAuthWorkos实例携带的是 auth 提供方自己拷贝的那份MastraAuthProvider声明而Mastra的server.auth期望的是mastra/core内部那份。3.1 三条具体规则文档给出了可操作的边界规则跨已发布包边界传递的类型必须是结构性的。不要依赖#private字段、protected成员或instanceof检查来建立跨边界的身份识别——应在公共契约点上暴露结构性接口如IMastraAuthProvider运行时使用鸭子类型duck-typing。被捆绑的类声明本身保留其品牌brand因此名义性的类类型如MastraAuthProvider绝不能出现在接收另一个已发布包实例的位置上。应该改收结构性接口并让类声明implements该接口让编译器在两份声明之间保持同步。回归测试覆盖在packages/core/src/server/server.test-d.ts接口可赋值性含模拟的捆绑副本与e2e-tests/type-check/template/core/auth.test-d.ts针对打包产物在exactOptionalPropertyTypes下运行。3.2 源码佐证IMastraAuthProvider 与模拟捆绑副本在 packages/core/src/server/server.test-d.ts 中IMastraAuthProvider structural boundary (#18682) 测试块原样印证了文档描述第一个用例验证SimpleAuth实例可以赋值给IMastraAuthProvider、可以直接塞进new CompositeAuth([...])和new Mastra({ server: { auth: ... } })第二个用例手工构造了一个BundledCopyProvider类模拟 auth 提供方在自己dist/中携带的声明副本拥有相同的公开表面component、name、toRawConfig()、authenticateToken()、authorizeUser()、mapUserToResourceId?却带着独立的#rawConfig与protected logger——即名义上互不兼容的两份类。测试断言这个副本实例依然可以赋值给IMastraAuthProvider并注入CompositeAuth与Mastra。这就是文档所说的如果IMastraAuthProvider将来引入任何重新触发名义检查的成员此赋值就会断裂的防护网。3.3 e2e 层验证打包产物 严格选项packages/core/src/server/server.test-d.ts 属于仓库内的类型测试而 e2e-tests/type-check/template/core/auth.test-d.ts 走的是更接近用户真实体验的路径测试从本地 registry 安装打包后的产物mastra/core、mastra/auth、mastra/auth-workos在exactOptionalPropertyTypes: true见模板内tsconfig.exact-optional.json这一曾暴露 bug 的严格开关下编译验证new Mastra({ server: { auth: workos } })与new Mastra({ studio: { auth: workos } })均可编译MastraAuthWorkos实例可赋值给来自mastra/core/server和mastra/auth两个来源的IMastraAuthProviderany提供方实例可进入CompositeAuth再注入Mastra。这条 e2e 链路把声明文件被打包内联与用户在 userland 编译两件事真正打通是第二节流水线的端到端验收。四、配套子模块embed-types 与 compile-zod除generateTypes()主入口外工具包还通过 package.json 的 exports 暴露两个子模块4.1 embed-types基于 API Extractor 的声明内联src/embed-types.js 提供了embedTypes(file, rootDir, bundledPackages)实现更彻底的内联方案先用流式读取判断声明文件是否包含捆绑包引用避免无谓地跑 API Extractor确认后调用microsoft/api-extractor以该声明文件为主入口、以tsconfig.build.json为编译配置做dtsRolluppublicTrimmedFilePath指向原文件把捆绑包类型真正折叠进单个声明文件。期间通过messageCallback捕获ae-forgotten-export警告把遗漏导出的符号用 ts-morph 以export声明补回确保回滚后公开类型不缺失。该模块是replaceTypes的 API-Extractor 版替代路径适合需要声明折叠的发布场景。4.2 compile-zod构建期编译 zod 模式为 JSON Schemasrc/compile-zod.js 导出一个 esbuild 插件esbuildCompileZod()与配套的compileSchema(schema)标识函数类型声明见 src/compile-zod.d.ts。其思路是在源码里用compileSchema(z.object({...}))标记 schema构建时在 esbuild 的onLoad钩子中把该调用原地替换为JSON.stringify后的 JSON Schema 字符串并删除对internal/types-builder/compile-zod的 import从而让运行时不再携带 zod 本体。实现上有两个值得关注的细节通过 zod 的 Standard Schema 接口schema[~standard].jsonSchema.input()求值 JSON Schema需要 zod v4替换前会用 TypeScript AST 做作用域分析isImportBindingsrc/compile-zod.js确保compileSchema这个局部名没有被函数参数、块级声明或顶层声明遮蔽避免误替换同名标识符。五、在仓库中的实际接线方式以mastra/core为例packages/core/tsdown.config.ts 在 tsdown 构建的onSuccess钩子中调用onSuccess: async () { await new Promise(resolve setTimeout(resolve, 1000)); await generateTypes( process.cwd(), new Set([ ai-sdk/*, eventsource-parser, internal/ai-sdk-v4, internal/ai-sdk-v5, internal/ai-v6, internal/ai-v7, internal/external-types, internal/core, internal/voice, hono, hono-openapi, internal/auth, ]), ); // ... 复制 provider-registry.json 与 capabilities/ 到 dist/ },这里传入的bundledPackages集合恰好与 tsdown 配置中deps.alwaysBundle列表packages/core/tsdown.config.ts一一对应两者共同保证了运行时被捆绑、声明也被内联的一致性。任何新增的alwaysBundle依赖都必须同步出现在generateTypes的bundledPackages中否则会被第三节的validateDeclarationRuntimeImports拦下——这正是该工具在 CI 中扮演的防漂移角色。六、实践要点与避坑清单结合文档与源码在为 Mastra 这样的多包仓库维护类型发布时需要记住跨包边界的公开契约用接口不用带品牌的类凡是要接收其他已发布包实例的位置如server.auth、CompositeAuth构造参数一律收IMastraAuthProvider这样的结构性接口类侧通过implements与接口保持同步。#private/protected/instanceof只在单一包内可信它们引入名义检查在内联副本场景下会静默制造不可赋值错误且往往只在exactOptionalPropertyTypes等严格配置下暴露。任何新捆绑的internal/*依赖都要加入bundledPackagesgenerateTypes(process.cwd(), new Set([...]))中的集合必须覆盖 tsdownalwaysBundle的全部条目并让校验器兜底。声明产物要在打包后状态验证仓库内类型测试如 server.test-d.ts与基于本地 registry 打包产物的 e2e 类型测试如 auth.test-d.ts缺一不可后者才真正等价于用户安装后的编译体验。构建脚本内联的声明要兼容 ESM 与 Windows相对导入需补.js后缀、目录导入需指向/index.js、路径分隔符必须归一化为/这些细节src/index.js 与 src/replace-types.js直接影响moduleResolution: bundler下的解析成败。七、总结internal/types-builder以一套四步流水线tsc 编译 → 类型内联到dist/_types/→ ESM 相对导入重写 → 运行时依赖校验解决了 monorepo 内部包被捆绑后声明文件悬空的问题而其最深的工程洞见在于当声明文件作为副本被分发给多个已发布包时类型系统必须退回到结构性检查的地基上。IMastraAuthProvider正是这一理念的落地产物它把 #18682 从一次用户侧编译事故转化为一条可被仓库内与 e2e 两层类型测试持续守护的边界规则。【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考