 发送类型安全的 GraphQL 文档)
后端【免费下载链接】graffleSimple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.项目地址https://gitcode.com/gh_mirrors/gr/graffle点击查看免费下载本篇指南聚焦 Graffle 的实例 APIInstance API——即创建客户端实例后通过client.gql()方法发送 GraphQL 文档的完整用法。它是 Graffle 文档体系中的发送环节在静态构建器Graffle.gql()构建出文档之后实例 API 负责把文档交给已配置 transport 的客户端执行并提供按操作名调用的类型化方法与.$send()灵活入口。读完本文你将掌握client.gql()支持的全部输入形态字符串、对象、预构建文档、遗留类型、Document Sender 发送器的两种执行方式、变量类型安全的工作机制以及背后的源码级实现原理。前置阅读本文建立在静态文档构建之上。建议先阅读该指南理解文档如何构建本文专注讲解如何使用客户端实例发送文档。实例 API 概述从文档到发送器一旦你拥有了一个 Graffle 客户端实例client.gql()就是发送 GraphQL 文档的入口。它属于实例 API——与静态构建器Graffle.gql()不同它要求客户端已经完成配置至少注册了 transport。import { Graffle } from graffle const client Graffle.create().transport({ url: https://api.example.com/graphql, }) // 发送一个 GraphQL 字符串 const sender client.gql( query getPokemon($name: String!) { pokemonByName(name: $name) { name hp } } ) // 使用操作名执行 const pokemon await sender.getPokemon({ name: Pikachu })从源码看gql是 ClientBase 上声明的方法gql: GqlMethod$Context其运行时实现在 client.ts 的createWithContext中先归一化参数再调用createDocumentSender生成发送器发送器内部通过sendRequest(context, request)进入请求管线。整个调用链是client.gql(doc)→GqlMethod.normalizeArguments→createDocumentSender(executeOperation)→executeOperation→sendRequest。client.gql() 接受什么四种输入形态client.gql()接受与静态构建器完全相同的输入字符串、对象同时额外支持预构建文档与遗留类型。以下是完整对照字符串stringGraphQL 语法字符串配合 GraphQLSP 等工具可获得类型推断const sender client.gql(query { pokemons { name } }) await sender.$send()对象objectTypeScript 对象语法基于 schema 的类型安全选择集const sender client.gql({ query: { getPokemons: { pokemons: { name: true, hp: true }, }, }, }) await sender.getPokemons()预构建文档pre-built由静态构建器10_static.md构建的文档直接传给实例import { Graffle } from ./graffle/_.js const doc Graffle.query.pokemons({ name: true, hp: true }) const client Graffle.create() await client.gql(doc).$send()遗留类型legacy来自graphql包的DocumentNode、TypedQueryDocumentNode以及 Graffle 自己的TypedDocument.Stringimport { parse } from graphql const doc parse( query pokemonByName($name: String!) { pokemonByName(name: $name) { name hp } } ) await client.gql(doc).$send({ name: Pikachu }) import { type TypedQueryDocumentNode } from graphql type PokemonDocument TypedQueryDocumentNode { pokemonByName: { name: string; hp: number } }, { name: string } const typedDoc parse( query pokemonByName($name: String!) { pokemonByName(name: $name) { name hp } } ) as PokemonDocument const result await client.gql(typedDoc).$send({ name: Pikachu }) import { type TypedDocument } from graffle type PokemonQuery TypedDocument.String { pokemonByName: { name: string; hp: number } }, { name: string } const data await client.gqlPokemonQuery( query pokemonByName($name: String!) { pokemonByName(name: $name) { name hp } } ).$send({ name: Pikachu })类型安全分级类型来源类型安全说明DocumentNodegraphql包无仅运行时无类型信息TypedQueryDocumentNodegraphql包有类型安全的文档节点TypedDocument.StringGraffle有类型安全的字符串文档这些类型通常由 GraphQL Code Generator 之类的代码生成工具产出。若希望不经过代码生成就获得原生类型安全请改用静态构建器。源码视角GqlMethod 的类型重载gql.ts 中的GqlMethod接口通过多条重载精确覆盖上述四种输入字符串重载string分支在配置了 GlobalRegistry 时走ParseGraphQLString解析出TypedFullDocument否则退化为UntypedSender对象重载通过ParseGraphQLObject把内联文档对象解析为类型化文档TypedDocumentLike 重载TypedDocumentNode、TypedDocumentString等直接生成对应发送器。归一化逻辑见 gql.ts 中的normalizeArguments判断依据是参数是否为字符串、是否含有definitionsDocumentNode或__meta__TypedDocumentNode属性从而区分类型化文档与文档对象两条处理路径。值得注意的两个工程细节模板字面量语法被明确拒绝。运行时实现client.ts会检测模板字面量调用并抛出Template literal syntax is not supported. Use call expression syntax instead错误测试用例见 gql.test.ts。正确写法是gql(query { id })而非gql\query { id }。SDDM 文档的编译期校验。gql.ts 中ValidateSDDMRequirement类型会在文档标注RequiresSDDMtrue例如携带自定义标量元数据时检查客户端是否配置了schema.map——缺失时直接产生类型错误this document requires SDDM but your client configuration lacks it防止 SDDM 文档被无 SDDM 能力的客户端执行。对应类型级断言见 gql.test-d.ts。Document Sender发送器的两种执行方式调用client.gql()后返回的是一个Document Sender对象提供两种执行操作的方式操作方法每个命名操作一个类型化方法与$send静态方法。操作方法Operation Methods文档中的每个命名操作都会变成发送器上的一个方法方法名即操作名const sender client.gql( query getPokemon($name: String!) { pokemonByName(name: $name) { name hp } } query getPokemons { pokemons { name } } ) const pokemon await sender.getPokemon({ name: Pikachu }) const pokemons await sender.getPokemons()对象语法同样支持多操作注意用$(name).required()声明必填变量import { $ } from graffle const sender client.gql({ query: { getPokemon: { pokemonByName: { $: { name: $(name).required() }, name: true, hp: true, }, }, getPokemons: { pokemons: { name: true, }, }, }, }) const pokemon await sender.getPokemon({ name: Pikachu }) const pokemons await sender.getPokemons()变量规则必填变量——必须提供.getPokemon({ name: Pikachu })可选变量——可以省略.getPokemons()或.getPokemons({ filter: { type: FIRE } })无变量——无参调用.getPokemons()TypeScript 会根据文档自动强制这些约束。类型定义在 DocumentSender.ts 中清晰可查RequiredVarsNamedExecutor要求(variables: $Variables)必传OptionalVarsNamedExecutor声明(variables?: $Variables)可省略NoVarsNamedExecutor则完全无参——三者由OperationToNamedExecutor按变量种类none/optional/required自动选择。运行时的代理实现发送器并非预生成所有方法而是由createDocumentSender用JavaScript Proxy动态实现——见 DocumentSender.ts对任意字符串属性除$send外返回(variables?) executeOperation(prop, variables)于是sender.getPokemon(...)在运行时被转换为一次以getPokemon为操作名的执行。这意味着操作方法的类型安全完全由编译期类型提供运行时不持有方法列表任何名字都能被代理接受但类型层面会拦截非法操作名。$send 静态方法Static Send Method$send提供运行时灵活性支持四种调用形态无参、仅变量、仅操作名、操作名变量。字符串语法await client.gql(query getPokemons { pokemons { name } }).$send() await client.gql( query getPokemon($name: String!) { pokemonByName(name: $name) { hp } }, ).$send( { name: Pikachu }, ) const sender client.gql( query getPokemon($name: String!) { pokemonByName(name: $name) { name hp } } query getPokemons { pokemons { name } } ) await sender.$send(getPokemon, { name: Pikachu }) await sender.$send(getPokemons)对象语法import { $ } from graffle await client.gql({ query: { getPokemons: { pokemons: { name: true }, }, }, }).$send() await client.gql({ query: { getPokemon: { pokemonByName: { $: { name: $(name).required() }, hp: true, }, }, }, }).$send({ name: Pikachu }) const sender client.gql({ query: { getPokemon: { pokemonByName: { $: { name: $(name).required() }, name: true, hp: true, }, }, getPokemons: { pokemons: { name: true }, }, }, }) await sender.$send(getPokemon, { name: Pikachu }) await sender.$send(getPokemons)$send的重载分发逻辑见 DocumentSender.ts 运行时实现当第一个参数是字符串时视为操作名否则视为变量对象。对应的类型重载包括单操作无变量SingleOpNoVarsStaticExecutor(operationName?) Promise...单操作可选变量SingleOpOptionalVarsStaticExecutor()、(variables?)、(operationName, variables?)三种签名单操作必填变量SingleOpRequiredVarsStaticExecutor(variables)、(operationName, variables)多操作MultiOpStaticExecutor泛型签名按操作名通过Extract判别从而对每个操作名推导出精确的变量与返回类型未类型化文档UntypedStaticExecutor接受任意操作名与变量返回Promiseunknown。匿名操作只能 $send()匿名操作没有操作名只支持.$send()因为操作方法是按名字注册的const sender client.gql(query { pokemons { name } }) await sender.$send()const sender client.gql({ query: { pokemons: { name: true }, }, }) await sender.$send()发送前的 Preflight 检查值得留意的是发送器的$send类型上还包裹了Configuration.Check.Preflight——见 DocumentSender.ts 的SenderStatic。当客户端未注册任何 transport、或当前 transport 未就绪时$send在类型层面就会变成对应的错误类型PreflightCheckNoTransportsRegistered、PreflightCheckTransportNotReady...而不是等到运行时才报错对应的类型级断言可在 gql.test-d.ts 末尾的 transport 测试段找到。操作名与变量的配合从文档到请求变量与操作名的绑定关系是发送的核心。以下展示一个同时包含必填变量与带默认值变量的示例字符串语法const sender client.gql( query pokemonDetails($name: String!, $includeStats: Boolean false) { pokemonByName(name: $name) { name hp include(if: $includeStats) attack include(if: $includeStats) } } ) await sender.pokemonDetails({ name: Pikachu, includeStats: true, })对象语法用$修饰符表达同样的约束import { $ } from graffle const sender client.gql({ query: { pokemonDetails: { pokemonByName: { $: { name: $(name).required(), includeStats: $(includeStats).optional().default(false), }, name: true, hp: { $include: $(includeStats), }, attack: { $include: $(includeStats), }, }, }, }, }) await sender.pokemonDetails({ name: Pikachu, includeStats: true, })源码视角变量输入种类如何决定调用签名发送器的调用签名并非手工编写而是由 graphql-kit 的 typed 模块 中的GetVariablesInputKind类型按变量对象结构自动推导输出三值none无变量、optional可选变量、required必填变量。推导规则依次是变量为never→ none带索引签名 → optional含必填键 → required仅有可选键 → optional无键 → none。这套分类正是 DocumentSender.ts 中所有 executor 类型分支判定的依据。类型级测试 gql.test-d.ts 系统性地验证了这些约束包括无变量文档$send()合法$send({})报ts-expect-error必填变量文档$send(getById, { id: })合法缺变量$send(getById)、类型错误$send({ id: 0 })都会被类型系统拒绝多操作文档必须提供操作名$send()报错操作名错误$send(bad)被拒绝未类型化文档as string$send()、$send(anyName)、$send({ any: vars })均返回unknown。真实示例与发送策略建议仓库的 examples 目录提供了可直接对照的完整示例gql_gql-string.ts使用字符串文档发送请求graffle.gql(...).$send()展示了匿名查询 $send()的最简用法gql_gql-document-node.ts使用parse()产出的 DocumentNode 发送请求并叠加了Throws与OpenTelemetry扩展演示了遗留类型在真实项目中的组合用法。实践中的选择建议用操作方法还是$send操作方法是静态调用的首选——IDE 自动补全、参数类型检查、返回类型推导都最完整$send适合操作名在运行时才确定、或需要在同一次发送中动态切换操作的场景如sender.$send(getPokemon, { name })。用字符串还是对象字符串适合从 GraphQL Playground 粘贴、迁移既有文档对象适合需要 IDE 自动补全全部字段、程序化生成文档的场景。两者在类型安全上等价按工作流偏好选择即可。类型安全最大化优先使用静态构建器产出的预构建文档或TypedDocument.StringDocumentNode仅用于纯运行时场景因为它不携带结果与变量类型。最后提醒本文介绍的client.gql()依赖客户端实例的 transport 配置。若你使用的是生成的类型化客户端其顶层gql方法行为一致但类型会叠加 schema 提供的 GlobalRegistry 约束未配置 transport 时发送会在类型层即被 Preflight 检查拦截见上文运行时也会抛出No transport selected。赞分享后端【免费下载链接】graffleSimple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.项目地址https://gitcode.com/gh_mirrors/gr/graffle点击查看免费下载相关推荐Graffle 使用 GraphQL 字符串文档发送请求graffle.gql() 实战指南Graffle 使用 GraphQL 字符串文档发送请求graffle.gql 实战指南 GraphQL 字符串是最直观、最接近原生 GraphQL 语法的文后端Graffle 实战使用 gql() 字符串文档发送 GraphQL 请求Graffle 实战使用 gql 字符串文档发送 GraphQL 请求 导读 Graffle 是一个极简、可扩展、类型安全且跨平台运行的 JavaScript后端Graffle 使用 GraphQL 字符串发送请求gql() 基础用法与 TypedDocument.String 类型安全实践Graffle 使用 GraphQL 字符串发送请求gql 基础用法与 TypedDocument.String 类型安全实践 导读 Graffle 的 gq后端上一篇还在手动保存抖音视频Douzy抖音下载器帮你一次打包整个主页下一篇10分钟免费搞定Steam创意工坊模组下载WorkshopDL新手实战手册创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考