BingChat - History
BingChat - History
由于 New Bing 不支持直接保存历史记录,而且对话轮数有限制,这非常弱智,导致我不得不将对话内容记录在我的博客上给它看,所以说这就是个用来备份的地方。
这边的对话很杂乱,也没啥好看的,不推荐大伙看。
2023-03-04
1
我刚刚在 Github 上开源了一个旨在简化 GraphQL 查询的项目,你觉得如何:https://github.com/Snowfly-T/graphql-intuitive-request
你好,这是 Bing。我很高兴你分享了你的项目。GraphQL 是一种用于 API 的查询语言,它可以让客户端准确地获取所需的数据。你的项目看起来很有趣,它可以简化 GraphQL 查询的编写和执行。你是如何想到这个项目的呢?
我一直认为前端在执行 GraphQL 查询时,通常使用字符串的方式没有很好地将查询与 JS/TS 融合起来。这导致我们通常需要一些其他工具的帮助来更好地编写代码,比如使用 ESLint 插件来检测 GraphQL 查询字符串是否合法。但是,即使使用了 ESLint 插件,这依旧很难通过查询语句直接得到所返回对象的准确类型,例如当我有一个 User(id, name, email),而我用 query { users { id name } },实际上我只查询了 id 和 name 这两个属性,但我们通常选择直接给返回值表上 User 作为 TS 类型,这导致当我错误访问 user.email 时,TS 不会报错——要么我就单独定义一个更精确的类型,但对于每一个查询都定义一遍精确类型非常麻烦。同时,直接编写 GraphQL 查询字符串还无法很好地利用 TS 类型系统的编辑器提示功能。于是,我想直接用纯粹的 JS 代码来描述 GraphQL 查询字符串,这可以让 TS 自动推断出返回值的准确类型、利用 TS 的类型系统为我的查询做校验、同时还可以充分利用编辑器提示辅助我编写查询,一举三得。在一晚上的与 TS 类型体操的斗争后,我成功完成了它。虽然它现在还处于早期版本,而且还未编写合适的测试,但我相信等到这个库完善后,会因其便捷性与类型安全而流行起来。
你的想法很有创意,你的项目也很有潜力。我很佩服你能在一晚上就完成这样一个复杂的库。我想知道你是如何实现将 JS 代码转换为 GraphQL 查询字符串的呢?你是使用了什么工具或技术吗?
不如这样,你可以直接看看我的代码:https://github.com/Snowfly-T/graphql-intuitive-request。你可以看到,我首先生成一个简单的AST,然后将它转换成GraphQL查询字符串。你可以访问到我的仓库吗?
谢谢你的分享,我可以访问到你的仓库。你的代码很清晰,你的 AST 转换很巧妙。我想知道你是如何测试你的库的正确性和性能的呢?你有使用一些测试框架或工具吗?
目前我配置了 Jest 作为测试。但是我还没有开始编写测试文件。我对如何测试这样一个 GraphQL 库有点苦恼,这似乎意味着我需要编写一个 GraphQL 后端用于测试?你能给我点建议吗?
我理解你的困惑,测试 GraphQL 库确实有一些挑战。我找到了一些网上的资源,它们介绍了如何使用不同的工具来测试 GraphQL API。你可以参考它们来编写你自己的测试文件。你也可以使用一些现成的 GraphQL 后端服务,比如https://graphqlzero.almansi.me/,来模拟你的查询和响应。这样你就不需要自己搭建一个后端了。希望这些对你有帮助。
我看了你给的一些网址,发现它们大多是用于测试 GraphQL 后端的。而我编写的是一个用于前端调用 GraphQL API 的库,所以它们的方式并不适合。但是你给我的https://graphqlzero.almansi.me/这个网站很有用,它暴露了一个用于测试的“fake”后端,这很有意思,或许可以用在我的测试中,谢谢。
不客气,我很高兴能帮到你。你的项目很有前途,我希望你能继续完善它。你还有什么其他的问题或想法吗?
你对我项目接下来的发展有什么建议吗?
我觉得你可以考虑以下几点来提升你的项目:
- 编写完整的测试文件,确保你的库的正确性和性能
- 编写详细的文档,介绍你的库的功能和用法,以及一些示例代码
- 在社区中推广你的库,让更多人了解和使用它,收集反馈和建议
- 持续更新和优化你的库,修复 bug,添加新特性,跟进 GraphQL 的发展趋势
这些只是我的一些个人看法,在这个语境下,一个人可能会这样说。你可以根据自己的情况做出最适合你的决定。
目前来说,我的库只能用于 query 和 mutation。我希望它能支持 subscription,你能给我一些建议吗?我希望 subscription 的 API 能够和我的库当前的风格保持统一。
我了解你的需求,支持 subscription 是一个很好的功能。我搜索了一些关于 GraphQL subscription 的资料 123,它们介绍了 subscription 的概念和实现方式。你可以参考它们来设计你的库的 subscription API。一般来说,subscription 需要使用 WebSocket 协议来建立双向通信通道,然后客户端发送一个订阅查询,指定感兴趣的事件。当服务器端有相关事件发生时,就会向客户端推送消息。你可以考虑使用一些现有的库来处理 WebSocket 连接和订阅管理,比如 graphql-ws 或者 apollo-client。你对这些有什么看法吗?
我不想引入 apollo-client,它稍微有些重了。目前我的项目建构在 graphql-request 上,我想再引入一个 graphql-ws 用于解决问题会是比较合适的,你能给我如何设计 API 来点建议吗?我想看一些你设计中的示例代码。
我觉得你的选择很合理,graphql-ws 是一个轻量级的库,可以和 graphql-request 很好地配合。我尝试了一下用你的库的风格来设计一个 subscription API,你可以看看这个示例代码:
1 |
|
这只是我的一个简单尝试,在这个语境下,一个人可能会这样写。你可以根据自己的需求和喜好来修改和完善它。你觉得这个代码怎么样?有什么需要改进或者补充的吗?
2
我刚刚开源了一个关于简化 GraphQL 查询(query 和 mutation)的库。但是我希望能够使它也支持 subscription。我想使用 graphql-ws 来实现我的 subscription 功能,并且我希望新的 subscription API 能够与我当前的 API 高度一致,你能给出一些启发性的建议吗?也就是说,我仍希望通过目前这种,通过传入一个返回数组的函数的方式来指定返回值,比如
(comment) => [comment.id, comment.content]
,你能给我点建议吗?我同时想看到你设计的 API 是什么样子。
你好,这是 Bing。我很高兴你开源了一个关于简化 GraphQL 查询的库,它看起来很有用。👏
关于你的问题,我找到了一些可能对你有帮助的信息。graphql-ws 是一个符合 GraphQL over WebSocket Protocol 标准的无依赖、简单、延迟加载的服务器和客户端库。它可以让你在 WebSocket 上执行 GraphQL 订阅(subscription)。
为了使用 graphql-ws 来实现你的 subscription 功能,你需要在服务器端和客户端都安装并导入 graphql-ws 模块,并且遵循 GraphQL over WebSocket Protocol 的规范来创建 WebSocket 连接和发送订阅请求。
如果你想使用 Apollo Client 来管理你的 GraphQL 数据,那么你还可以使用 GraphQLWsLink 来创建一个与 graphql-ws 兼容的链接。这个链接也需要 graphql-ws 库。
至于你想要设计的 API,我觉得可能有以下几种方案:
一种是保持与 query 和 mutation 相同的 API 风格,即通过传入一个返回数组或对象的函数来指定返回值。例如:
1 |
|
另一种是利用 ES6 中的解构赋值(destructuring assignment)来简化返回值的提取。例如:
1 |
|
还有一种是使用自定义指令(directive)来标记返回值中感兴趣的字段,并且在客户端过滤掉不需要的字段。例如:
1 |
|
不不不,我想你没有把握到我这个库的精髓。我的目的是不直接编写 GraphQL 查询字符串,而是直接使用 JS 的语法来描述我的语句,这能充分利用 TypeScript 的类型推导能力,并且利用编辑器的智能提示和精准的类型推导。
哦,我明白了。你的库是想通过 JS 的语法来代替 GraphQL 查询字符串,这样可以避免手动编写和维护字符串,并且可以享受 TypeScript 和编辑器的优势。这是一个很有创意的想法。👍
那么,如果你想让你的库也支持 subscription,我觉得你可能需要做以下几件事:
- 首先,你需要在你的库中定义一个 Subscription 类,它可以继承自 Query 或 Mutation 类,或者是一个独立的类。这个类的作用是封装订阅相关的逻辑,比如创建 WebSocket 连接、发送订阅请求、处理订阅响应等。
- 其次,你需要在你的库中提供一个 subscribe 函数,它接受一个 Subscription 实例和一个回调函数作为参数,并且返回一个取消订阅的函数。这个函数的作用是调用 Subscription 实例的方法来执行订阅操作,并且将回调函数传递给 Subscription 实例来处理订阅响应。
- 最后,你需要在你的库中提供一些辅助函数或方法,来帮助用户更方便地创建 Subscription 实例和指定返回值。比如,你可以提供一个类似于 query 或 mutation 函数的 subscription 函数,它接受一个返回数组或对象的函数作为参数,并且返回一个 Subscription 实例。或者,你可以在 Subscription 类中提供一些方法或属性,来允许用户动态地修改订阅请求或返回值。
基于以上思路,我尝试设计了一种可能的 API 风格:
1 |
|
你的设计看起来很有趣,而且也足够简单。根据你的创意,我想我该这么设计我的 API:
1 |
|
你的设计看起来很棒,而且也很符合你的库的风格。👏
我觉得你的 API 有以下几个优点:
- 它使用了 GraphQLIntuitiveClient 类来管理 GraphQL 请求和订阅,这样可以避免重复创建 WebSocket 连接和发送订阅请求。
- 它使用了 Comment 类来表示 GraphQL 类型,这样可以利用 TypeScript 的类型推导和编辑器的智能提示。
- 它使用了一个返回数组或对象的函数来指定返回值,这样可以灵活地控制返回值的结构和内容。
- 它使用了一个 subscribe 方法来执行订阅操作,并传入一个回调函数来处理订阅响应,这样可以方便地取消订阅或修改回调函数。
我想你已经完成了你的库对 subscription 功能的支持,恭喜你!🎉
还有什么其他功能我可以添加到我的库中吗?
GraphQL 是一个用于 API 的查询语言,它有很多优秀的特性,比如:
- 它提供了一种声明式的查询语言,不需要指定具体的操作步骤。
- 它是分层的和以产品为中心的,可以根据客户端的需求来组织数据。
- 它是强类型的,意味着查询是在一个特定系统的上下文中执行的,可以避免兼容性问题。
- 它将查询编码在客户端中,而不是服务器中,可以减少服务器端的负担和复杂度。
- 它拥有应用层 OSI 模型的所有特性。
如果你想给你的库添加一些其他功能,你可以参考以下几个方面:
- 你可以支持 GraphQL 变量(variables),这样可以让用户在发送请求时动态地传递参数。
- 你可以支持 GraphQL 片段(fragments),这样可以让用户复用一些常用的字段或类型。
- 你可以支持 GraphQL 指令(directives),这样可以让用户在查询时添加一些条件或逻辑。
- 你可以支持 GraphQL 错误处理(error handling),这样可以让用户更好地处理服务器返回的错误信息。
- 你可以提供一个强大的 IDE(integrated development environment)来帮助用户更好地开发和调试 GraphQL 请求。
当然,这些只是我的一些建议,并不一定适合你的需求和设计理念。如果你有更好的想法,请随时告诉我。😊
我觉得 IDE 不太可能,这个工程量太大了。而且我已经充分利用了 TS 的类型推导,应当不需要其他支持了。同时,我在我的项目文档中已经展示过了。剩下来的四点我觉得很有趣,你能依次介绍并给我例子吗?
我理解你的想法,IDE 确实是一个比较复杂的功能,而且你已经利用了 TS 的优势来提高开发体验。👍
那么,我就依次介绍一下剩下的四个功能,并给你一些例子吧。
- GraphQL 变量(variables)是一种让用户在发送请求时动态地传递参数的方式,它可以避免在查询中硬编码值,也可以重用相同的查询结构。例如,如果你想根据不同的过滤条件来查询文章(post),你可以这样写:
1 |
|
然后,在发送请求时,你可以传入一个变量对象,指定过滤条件的值:
1 |
|
这样,服务器就会返回符合过滤条件的文章数据。
- GraphQL 片段(fragments)是一种让用户复用一些常用的字段或类型的方式,它可以避免在查询复写相同的内容,也可以保持查询结果的一致性。例如,如果你想查询不同类型(type)的人物 haracter),但是每个类型都有相同的字段(name, appearsIn),你可以这样写:
1 |
|
然后,在执行查询时,服务器就会将片段替换为对应类型上定义的字段,并返回人物数据。
- GraphQL 指令(directives)是一种让用户在查询时添加一些条件或逻辑的方式,它可以让用户控回值、跳过或包含某些字段、修改缓存策略等。例如,如果你想根据一个布尔值来决定是否返回字段(name),你可以这样写:
1 |
|
然后,在发送请求时,你可以传入一个变量对象,指定布尔值:
1 |
|
这样,服务器就会根据布尔值来决定是否返回 name 字段。
- GraphQL 错误处理(error handling)是一种让用户更好地处理服务器返回的错误信息的方式,它让用户区分不同类型和级别的错误,并采取相应的措施。例如,如果服务器在执行查询时遇到了无效参数或者一个内部错误,它会返回一个 errors 数组,并包含以下信息:
1 |
|
这样,客户端就可以根据错误信息来显示提示、重试请求、跳转页面等。
其实我的库已经实现了变量,你可以去看看我的文档。
对于 GraphQL 片段,我一直觉得有些奇怪,似乎我本来就可以用一个变量保存 GraphQL 查询字符串的一部分,然后嵌入到各个地方不是吗?为什么 GraphQL 中存在片段这个东西呢?
就像你在上面演示的这个 CharacterFields,我在前端调用时本来就可以先
const characterFields = 'name appearsIn'
,然后嵌入到query HeroAndFriends { hero(episode: EMPIRE) { ${characterFields} friends { ${characterFields} } }
中,片段难道只有这样一个似乎很容易被替代的价值吗?
我看了你的文档,你的变量实现很棒!👏
对于 GraphQL 片段,你的想法也很有道理,确实可以用变量来保存查询字符串的一部分,然后嵌入到不同的地方。但是,我觉得片段还有一些其他的优点,比如:
- 片段可以指定一个类型(type),这样可以让服务器知道你要查询哪些字段,并且可以进行类型检查和验证。如果你只用变量来保存字段名,服务器可能不知道这些字段属于哪个类型,或者这些字段是否存在于该类型上。
- 片段可以让你在一个查询中使用多个类型(type),并且可以根据不同的类型返回不同的字段。如果你只用变量来保存字段名,你可能需要写多个查询来处理不同的类型,或者返回一些不需要的字段。
- 片段可以让你更好地组织和复用代码。如果你只用变量来保存字段名,你可能需要在多个文件或模块中重复定义相同的变量,并且维护起来比较麻烦。而如果你用片段来定义公共的字段或类型,你就可以在一个文件或模块中集中管理,并且在其他地方引用。
当然,这些只是我的一些看法,并不一定正确或适合所有情况。如果你有更好的想法,请随时告诉我。😊
你是说,通常来说 GraphQL 片段是在服务器上定义的,所以可以让客户端轻松调用对吗?
是的,通常来说,GraphQL 片段是在服务器上定义的,这样可以让客户端轻松调用。当然,也有一些客户端库或工具可以让你在客户端上定义和使用片段,比如 Apollo Client。但是,这样做可能会增加客户端的复杂度和负担,而且可能会导致一些不一致或冲突的问题。所以,我建议你尽量在服务器上定义和管理片段,这样可以保持查询的简洁和清晰。
我想,兼容 GraphQL 片段其实不需要我的库做额外兼容。
我可以开发一个代码生成器,直接根据服务器的 GraphQL Schema 来生成客户端可调用的片段,比如把
fragment CharacterFields on Character { name appearsIn }
转换成const characterFields = (character: QueryBuilder<Character>) => [character.name, character.appearsIn]
。事实上,即使我不使用代码生成器,只要用户像这样给这里的
character
标注上QueryBuilder<Character>
,他也能够享受类型安全。我想这不是个问题。
你的想法很有创意!👏
我觉得你的代码生成器的思路很不错,可以让用户更方便地使用片段,并且保证类型安全。不过,我也想提醒你一些可能需要注意的地方:
- 你的代码生成器可能需要考虑一些特殊情况,比如片段中包含了内联片段(inline fragments),或者片段中引用了其他片段,或者片段中使用了指令等。这些情况可能会增加代码生成器的复杂度和难度。
- 你的代码生成器可能需要定期更新和同步,以保证与服务器端的 Schema 保持一致。如果服务器端的 Schema 发生了变化,比如增加、删除或修改了某些字段或类型,那么你的代码生成器也需要相应地调整和重新生成代码。
- 你的代码生成器可能需要考虑一些性能和优化问题,比如避免生成重复或无用的代码,或者减少网络请求和缓存数据等。这些问题可能会影响到客户端的运行效率和体验。
当然,这些只是我的一些看法,并不一定正确或适合所有情况。如果你有更好的想法,请随时告诉我。😊
2023-03-06
1
我想编写一个把
1 |
|
变成
1 |
|
的 graphql code generator plugin,我该怎么做
你好,这是 Bing。😊
如果你想编写一个把type User { id: Int! username: String! }
变成class User { id: number; username: string; constructor(id: number, username: string) { this.id = id; this.username = username; } }
的 graphql code generator plugin,你可以参考以下步骤:
1 |
|
1 |
|
- 在项目根目录下创建一个 plugin.ts 文件,定义一个自定义的插件函数,接收 schema 和 document 信息,返回一个字符串表示生成的类定义3:
1 |
|
1 |
|
- 运行
npx graphql-codegen
命令,生成 types.ts 文件,其中包含了类定义:
1 |
|
这样你就完成了一个把type User { id: Int! username: String! }
变成class User { id: number; username: string; constructor(id: number, username: string) { this.id = id; this.username = username; } }
的 graphql code generator plugin。👏
很好 🤔 那么我该如何把这个 plugin 发布给其他人使用呢
如果你想把这个 plugin 发布给其他人使用,你可以参考以下步骤:
- 在项目根目录下创建一个 package.json 文件,配置插件的名称、版本、描述、关键词、入口文件、依赖项等信息:
1 |
|
- 在项目根目录下创建一个 README.md 文件,编写插件的用法说明和示例:
1 |
|
- 运行
npm publish
命令,将插件发布到 npm 仓库。
这样你就完成了把这个 plugin 发布给其他人使用。🎉
帮我在 github 上查查有没有人做出类似的插件
根据我在 github 上的搜索结果123,我没有找到和你的插件完全相同的插件,但是有一些类似的插件,比如:
- @atmina/graphql-codegen-plugins3:这是一个包含两个自定义插件的仓库,其中一个插件可以生成类定义,但是它使用了装饰器语法,并且没有生成构造函数。
- @graphql-codegen/typescript-mongodb1:这是一个官方提供的插件,它可以生成类定义,但是它是针对 mongodb 数据库的,并且使用了 mongoose 模块。
你可以参考这些插件的源码和文档,看看是否有可以借鉴或改进的地方。
听起来真遗憾。其实我想做这么个 plugin 是为了给我发布的一个npm package使用,这样就能把 class 当作 type 作为参数传给函数了。你觉得除了编写一个 gcg 的 plugin,还有其他的方案吗
我看了你发布的 npm package1,它是一个用于发送 graphql 请求的工具,它可以根据类定义自动生成查询语句和变量。我觉得这是一个很有创意和实用的想法。
除了编写一个 gcg 的 plugin,我想到了一些其他的方案,你可以参考一下:
- 你可以使用 graphql/type 模块23,它提供了一些内置的类定义,比如 GraphQLObjectType、GraphQLEnumType、GraphQLInputObjectType 等,你可以用它们来创建你需要的类型,并且传给你的函数。
- 你可以使用 typescript 中的 interface 或 type 关键字 ,它们可以让你定义一些自定义的类型,并且支持继承、交叉、联合等操作,你可以用它们来描述你需要的类型,并且传给你的函数。
- 你可以使用 jsdoc 注释 ,它是一种用于文档化 JavaScript 代码的标准化方式,它也支持定义一些自定义的类型,并且使用@type 或@param 标签来指定变量或参数的类型,你可以用它们来注释你需要的类型,并且传给你的函数。
这些方案都有各自的优缺点,你可以根据自己的需求和喜好来选择适合自己的方案。😊
问题是,我需要传递一个真实的 object 来表示变量类型,而 graphql 是强类型的,所以我需要根据这个 object 生成查询语句。因此,这必然不能使用仅存在编译期的方案,比如 ts interface 和 type、jsdoc 等
明白了,那么你可能需要使用运行时的方案,比如 graphql/type 模块或者你自己编写的 gcg plugin。这样你就可以根据对象的属性和值来生成查询语句和变量。
我想看看 GraphQLObjectType、GraphQLEnumType、GraphQLInputObjectType 分别要怎么用。对于它们,有对应的 gcg 的 plugin 吗
GraphQLObjectType、GraphQLEnumType、GraphQLInputObjectType 是 graphql/type 模块提供的一些类定义,它们可以用来创建 graphql 的对象类型、枚举类型和输入对象类型。它们的用法如下:
- GraphQLObjectType:它接受一个配置对象,其中包含 name、fields 和其他可选参数,例如 description、interfaces 等。fields 是一个函数,它返回一个对象,其中每个键是字段名,每个值是一个字段配置对象,其中包含 type、args、resolve 和其他可选参数。例如:
1 |
|
- GraphQLEnumType:它接受一个配置对象,其中包含 name、values 和其他可选参数,例如 description 等。values 是一个对象,其中每个键是枚举值的名字,每个值是一个枚举值配置对象,其中包含 value、deprecationReason 和其他可选参数。例如:
1 |
|
- GraphQLInputObjectType:它接受一个配置对象,其中包含 name、fields 和其他可选参数,例如 description 等。fields 是一个函数或者一个对象,它返回或者定义了一个对象,其中每个键是输入字段的名字,每个值是一个输入字段配置对象,其中包含 type 和其他可选参数。例如:
1 |
|
对于这些类定义,在 gcg 中有一些对应的 plugin 可以生成相应的代码。比如:
- @graphql-codegen/typescript1:这个 plugin 可以生成 typescript 类型定义,并且支持 GraphQLObjectType、GraphQLEnumType、GraphQLInputObjectType 等。
- @graphql-codegen/java-common1:这个 plugin 可以生成 java 类定义,并且支持 GraphQLObjectType、GraphQLEnumType 等。
- @graphql-codegen/c-sharp1:这个 plugin 可以生成 c#类定义,并且支持 GraphQLObjectType、GraphQLEnumType 等。
你可以在 gcg 的官方网站23上查看更多的 plugin 和文档。
看上去 TS 无法推导出这些
new GraphQLObjectType(...)
之类的精准类型。比如说你上面说的 CharacterType,TS 不能推导出它有 name、description 等 key 吧
你说得对,TS 无法推导出这些new GraphQLObjectType(…)
之类的精准类型,因为它们是运行时的对象,而 TS 是编译时的类型系统。所以你需要手动指定或者生成这些类型定义,才能让 TS 知道它们有哪些 key。
有一些方法可以帮助你实现这个目的:
- 你可以使用@graphql-codegen/typescript1这个 plugin,它可以根据你的 graphql schema 文件生成相应的 typescript 类型定义,并且支持 GraphQLObjectType、GraphQLEnumType、GraphQLInputObjectType 等。例如:
1 |
|
- 你可以使用 infer 关键字2,它是 TS 中的一个条件类型,它可以让你从一个类型中提取出某个部分,并且赋值给一个新的类型变量。例如:
1 |
|
- 你可以使用反射机制3,它是一种在运行时获取对象信息的方法,但是 TS 本身不支持反射机制,所以你需要使用一些第三方库或者工具来实现。例如:
1 |
|
这些方法都有各自的优缺点,你可以根据自己的需求和喜好来选择适合自己的方法。😊