拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TypeGraphQL 的 @Extensions 装饰器实战:为 GraphQL Schema 注入自定义元数据并在运行时消费

TypeGraphQL 的 @Extensions 装饰器实战:为 GraphQL Schema 注入自定义元数据并在运行时消费 后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载本文以 TypeGraphQL 官方文档website/versioned_docs/version-2.0.0-rc.1/extensions.md当前版本对应docs/extensions.md为骨架结合仓库源码src/decorators/Extensions.ts、src/metadata/metadata-storage.ts、src/schema/schema-generator.ts与单元测试tests/functional/extensions.ts展开。读完本文你将掌握extensions元数据机制在 GraphQL 中的价值、Extensions装饰器的全部用法多次装饰、键覆盖、继承合并、可注解的目标范围以及如何在 middleware/resolver 中读取扩展数据实现自定义业务逻辑并能结合源码理解其底层工作方式。为什么需要extensions把自定义元数据挂到 Schema 上graphql-js库允许在 GraphQL 类型的配置对象中通过extensions属性存放任意数据。这为 Schema 描述层之外的元信息提供了一个官方扩展点——比如权限角色、日志等级、复杂度估算、可见性开关等都可以作为自定义元数据附着在类型、字段或操作上。在 TypeGraphQL 中类型和字段是通过 TypeScript 类与装饰器声明的因此我们需要一种声明式的手段把这些自定义数据写进可执行 Schema 的extensions属性。为此TypeGraphQL 提供了Extensions装饰器它会把我们定义的数据附加到被装饰的类Class、方法Method或属性Property所对应的可执行 Schema 节点上。注意这是一个底层low-level装饰器TypeGraphQL 本身不会消费这些元数据你需要自己编写读取逻辑如在中间件或 resolver 中来发挥它的作用。从源码看Extensions接收的参数类型定义为ReadonlyRecordstring, any见src/metadata/definitions/extensions-metadata.ts也就是说它接受一个任意键值对对象且该对象在收集阶段不会被修改。使用Extensions装饰器基础用法给 Schema 类型附加扩展数据只需使用Extensions装饰器并传入一个自定义数据对象Extensions({ complexity: 2 })也可以一次传入多个字段Extensions({ logMessage: Restricted access, logLevel: 1 })装饰器可以叠加使用。下面这段代码与上面单次传入两个字段的效果完全相同——装饰器会把多次传入的数据合并成一个对象Extensions({ logMessage: Restricted access }) Extensions({ logLevel: 1 })如果多次装饰时使用了相同的键则以定义在**下方后执行**的装饰器为准后者覆盖前者Extensions({ logMessage: Restricted access }) Extensions({ logMessage: Another message })上面的用法最终会让该 GraphQL 类型的extensions中出现logMessage: Another message属性。这个后者覆盖前者、不同键合并的行为在源码中有明确印证元数据存储类的findExtensions方法src/metadata/metadata-storage.ts使用reduce((extensions, entry) ({ ...extensions, ...entry.extensions }), {})依次展开合并因此键冲突时靠后的条目覆盖靠前的条目键不冲突时则自然合并。哪些目标可以注解ExtensionsTypeGraphQL 中带以下装饰器的类/成员都可以被Extensions注解ObjectType—— 对象类型类InputType—— 输入类型类Field—— 类型字段Query—— 查询操作Mutation—— 变更操作FieldResolver—— 字段解析器也就是说Extensions既可以放在类的属性/方法上也可以放在类型类本身之上还可以视需要叠加多次具体取决于你希望扩展数据挂在哪一层。完整用法示例Extensions({ roles: [USER] }) ObjectType() class Foo { Field() field: string; } ObjectType() class Bar { Extensions({ roles: [USER] }) Field() field: string; } ObjectType() class Bar { Extensions({ roles: [USER] }) Extensions({ visible: false, logMessage: User accessed restricted field }) Field() field: string; } Resolver(of Foo) class FooBarResolver { Extensions({ roles: [USER] }) Query() foobar(Arg(baz) baz: string): string { return foobar; } Extensions({ roles: [ADMIN] }) FieldResolver() bar(): string { return foobar; } }从测试代码tests/functional/extensions.ts可以看到实际测试中还覆盖了InterfaceType接口类型类与接口字段、接口继承链、对象类型实现接口等场景扩展数据都能正确传递。测试还验证了当Field与FieldResolver都声明了 extensions 时两者会合并到同一个字段的extensions对象上见该文件中 Fields with field resolvers 一节childField: true与childFieldResolver: true同时出现。源码原理元数据如何流入可执行 Schema理解Extensions的底层机制能帮你更准确地预判各种用法叠加、继承、覆盖的最终结果。整体链路分为三步装饰器收集Extensions的实现src/decorators/Extensions.ts根据是否存在propertyKey决定收集粒度——有propertyKey时调用collectExtensionsFieldMetadata字段级否则调用collectExtensionsClassMetadata类级并统一存入全局元数据存储src/metadata/metadata-storage.ts。若属性键是Symbol会抛出SymbolKeysNotSupportedError定义于src/errors/SymbolKeysNotSupportedError.ts这是装饰器对不支持场景的显式保护。Schema 生成映射在buildSchema生成可执行 Schema 时src/schema/schema-generator.ts会把收集到的元数据写入对应节点的extensions配置对象类型类extensions: objectType.extensionsschema-generator.ts对象类型字段extensions: { complexity: field.complexity, ...field.extensions, ...fieldResolverMetadata?.extensions }schema-generator.ts——注意字段级 extensions 会与内置的complexity由Field({ complexity })或全局 complexity 选项设置合并且FieldResolver上的 extensions 会以更高优先级合并进来接口类型类与接口字段extensions: interfaceType.extensionsschema-generator.ts及字段级合并schema-generator.ts输入类型类与输入字段分别见schema-generator.ts与schema-generator.tsQuery/Mutation 等 resolver 处理器extensions: { ...handler.extensions }schema-generator.ts。继承与合并语义findExtensions在筛选时使用Object.prototype.isPrototypeOf.call(entry.target, target)判断继承关系src/metadata/metadata-storage.ts因此父类上声明的类级/字段级 extensions 会被子类继承。这一点在测试中也有专门验证Inheritance 一节Child的extensions同时包含parentClass: true与childClass: true而父类Parent不会反向获得子类的扩展数据父类的字段扩展parentField: true也会出现在子类对象的同名字段上。综合来看extensions的合并规则可以总结为场景行为单次传入多个键全部写入 extensions 对象多次装饰、键不冲突各次数据合并为一个对象多次装饰、键冲突靠下后执行的装饰器覆盖靠上先执行的字段 FieldResolver 同时注解两者合并FieldResolver 的优先级更高父类注解、子类继承类级与字段级 extensions 均被继承父类不受子类影响运行时消费在中间件与 Resolver 中读取 extensions一旦装饰完成可执行 Schema 的对应节点上就带上了extensions数据你可以用任意方式消费它。最常见的场景是在 resolver 或 middleware 中读取它执行自定义逻辑。下面的例子是一个全局中间件当某个字段用Extensions注解了logMessage时该中间件会在字段解析器执行时打印这条日志export class LoggerMiddleware implements MiddlewareInterfaceContext { constructor(private readonly logger: Logger) {} use({ info }: ResolverData, next: NextFn) { // 从 GraphQLResolveInfo 对象中取出 extensions拿到 logMessage 的值 const { logMessage } info.parentType.getFields()[info.fieldName].extensions || {}; if (logMessage) { this.logger.log(logMessage); } return next(); } }关键读取路径为info.parentType.getFields()[info.fieldName].extensionsinfo是GraphQLResolveInfoparentType是当前字段所属的对象类型getFields()返回该类型的字段映射再用info.fieldName取到当前正在执行的字段定义最终访问其extensions属性。由于没有注解的字段没有extensions对象代码中用|| {}做了兜底避免解构报错。读取到数据后可以做任何自定义逻辑权限判断、日志记录、字段可见性控制、复杂度计算、A/B 开关等。TypeGraphQL 仓库中的examples/middlewares-custom-decorators与docs/middlewares.md展示了中间件的完整接入方式如通过buildSchema的globalMiddlewares选项注册全局中间件或用UseMiddleware按需挂载可以与本篇的 extensions 读取示例结合使用。测试验证行为边界一目了然仓库的单元测试tests/functional/extensions.ts为上文总结的行为提供了可复现的证据它通过buildSchema构造真实 Schema 后断言各节点extensions的内容对象字段简单扩展、多属性扩展、多次装饰合并first/second/third三个值同时存在、重复键时后者覆盖duplicate: second valueQuery / MutationExtensions同样作用于操作字段且多次装饰会合并ObjectType / InputType类级 extensions如{ id: 1234 }、{ roles: [admin, user] }正确写入类型节点输入字段级扩展同样生效FieldResolverFieldResolver上的扩展数据出现在目标类型的对应字段上接口Interface接口类与接口字段的扩展数据会出现在实现该接口的对象类型、继承接口的接口类型以及多层继承链上继承Inheritance子类获得父类的类级与字段级扩展父类不受子类影响字段 FieldResolver 共存两层扩展合并到同一字段。这些断言与extensions的最终产物直接对应读者可以用npm test仓库测试配置见jest.config.cts在本地跑一遍直观确认行为。注意事项与最佳实践结合文档说明与源码实现使用Extensions时建议注意以下几点它是底层机制需要自备消费逻辑Extensions只负责把数据写进可执行 Schema不会主动产生任何运行时效果若你需要开箱即用的能力如权限控制优先考虑 TypeGraphQL 的Authorized 自定义authChecker见docs/authorization.md或UseMiddleware中间件体系Extensions适合承载这些机制之外的、纯自定义的元数据。数据应当可序列化extensions最终会出现在 Schema 的内存表示中若配合emitSchemaFile/printSchema等工具输出 SDL见docs/emit-schema.md需要注意数据是否适合暴露在 Schema 描述中通常建议只放简单值字符串、数字、布尔、数组、普通对象。与complexity的合并关系字段级extensions会与查询复杂度complexity合并进同一对象见schema-generator.ts不要使用complexity这个键作为自定义扩展名以免与内置复杂度机制冲突复杂度相关用法见docs/complexity.md。Symbol 属性键不支持装饰器对 symbol 类型的属性键会抛出SymbolKeysNotSupportedError请使用字符串字段名。善用多次装饰 继承组织元数据利用合并与继承语义可以把公共元数据放在父类/基类把差异化元数据叠加在子类避免重复声明。小结Extensions是 TypeGraphQL 中连接声明式类定义与graphql-js 底层 extensions 配置的桥梁一次装饰、多处生效元数据最终进入可执行 Schema 的对象类型、接口、输入类型、字段以及 Query/Mutation 操作节点供中间件与 resolver 在运行时读取。理解其合并、覆盖与继承规则均可通过tests/functional/extensions.ts验证再配合中间件机制docs/middlewares.md、examples/middlewares-custom-decorators你就可以为 Schema 附加任意自定义语义构建出灵活、可维护的 GraphQL 服务。延伸阅读完整装饰器列表见docs/custom-decorators.mdExtensions涉及的元数据类型定义见src/metadata/definitions/extensions-metadata.ts装饰器实现见src/decorators/Extensions.ts。赞分享后端GraphQLAPI设计【免费下载链接】type-graphqlCreate GraphQL schema and resolvers with TypeScript, using classes and decorators!项目地址https://gitcode.com/gh_mirrors/ty/type-graphql点击查看免费下载相关推荐TypeGraphQL Extensions 装饰器实战为 GraphQL Schema 注入自定义元数据并在运行时消费TypeGraphQL Extensions 装饰器实战为 GraphQL Schema 注入自定义元数据并在运行时消费 在构建 GraphQL API 时后端GraphQLAPI设计TanStack Solid Start 路径别名Path Aliases配置完全指南从 tsconfig.json 到 Vite / Rsbuild 端到端打通TanStack Solid Start 路径别名Path Aliases配置完全指南从 tsconfig.json 到 Vite / Rsbuild 端后端GraphQLAPI设计TypeGraphQL 扩展元数据实战用 Extensions 装饰器向 GraphQL Schema 注入自定义数据TypeGraphQL 扩展元数据实战用 Extensions 装饰器向 GraphQL Schema 注入自定义数据 导读 TypeGraphQL 允许通后端GraphQLAPI设计上一篇云微信部署指南把微信常驻服务器多设备共享同一会话下一篇如何构建《正义之怒》完整灵使剑圣重击借机流一刀重击连锁清屏创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表