FSharp.Data.GraphQL.Client 2.2.1
dotnet add package FSharp.Data.GraphQL.Client --version 2.2.1
NuGet\Install-Package FSharp.Data.GraphQL.Client -Version 2.2.1
<PackageReference Include="FSharp.Data.GraphQL.Client" Version="2.2.1" />
paket add FSharp.Data.GraphQL.Client --version 2.2.1
#r "nuget: FSharp.Data.GraphQL.Client, 2.2.1"
// Install FSharp.Data.GraphQL.Client as a Cake Addin #addin nuget:?package=FSharp.Data.GraphQL.Client&version=2.2.1 // Install FSharp.Data.GraphQL.Client as a Cake Tool #tool nuget:?package=FSharp.Data.GraphQL.Client&version=2.2.1
FSharp.Data.GraphQL
F# 实现 Facebook GraphQL 查询语言规范。
安装项目模板
键入以下命令来安装创建 Web 应用程序的模板
从 GitHub:dotnet new -i "FSharp.Data.GraphQL::2.0.0-ci-*" --nuget-source "https://nuget.pkg.github.com/fsprojects/index.json"
从 NuGet:dotnet new -i "FSharp.Data.GraphQL::2.0.0"
快速入门
open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Types
type Person =
{ FirstName: string
LastName: string }
// Define GraphQL type
let PersonType = Define.Object(
name = "Person",
fields = [
// Property resolver will be auto-generated
Define.AutoField("firstName", StringType)
// Asynchronous explicit member resolver
Define.AsyncField("lastName", StringType, resolve = fun context person -> async { return person.LastName })
])
// Include person as a root query of a schema
let schema = Schema(query = PersonType)
// Create an Exector for the schema
let executor = Executor(schema)
// Retrieve person data
let johnSnow = { FirstName = "John"; LastName = "Snow" }
let reply = executor.AsyncExecute(Parser.parse "{ firstName, lastName }", johnSnow) |> Async.RunSynchronously
// #> { data: { "firstName", "John", "lastName", "Snow" } }
它是类型安全的。无效字段或无效返回类型等将在编译时进行检查。
ASP.NET / Giraffe / WebSocket (用于 GraphQL 订阅) 的使用
演示
GraphiQL 客户端
转到 GraphiQL 示例目录。为了运行它,以Debug设置构建并运行 Star Wars API 示例项目 - 这将创建一个与 GraphQL 规范兼容的 Giraffe 服务器,在 8086 端口运行。然后您需要运行 node.js graphiql 前端。要这样做,运行 npm i
以获取所有依赖项,然后运行 npm run serve | npm run dev
- 这将在 http://localhost:8090/ 上运行 webpack 服务器。访问此链接,GraphiQL 编辑器应该会出现。您可以尝试以下查询
{
hero(id:"1000") {
id,
name,
appearsIn,
homePlanet,
friends {
... on Human {
name
}
... on Droid {
name
}
}
}
}
Relay.js 快速入门套件
第二个示例是流行的 Relay Starter Kit 的 F# 版本 - 这是一个使用 React.js + Relay 及与其兼容的服务器 API 的示例应用程序。[查看](https://github.com/bazingatechnologies/FSharp.Data.GraphQL/tree/dev/samples/relay-starter-kit)
要运行它,请使用调试设置构建 FSharp.Data.GraphQL
和 FSharp.Data.GraphQL.Relay
项目。然后在 FSI 中运行 server.fsx
脚本以启动服务器 - 这将在端口 8083 上启动一个兼容 Relay 的 F# 服务器。然后通过运行 npm i
获取所有依赖项并在终端中运行它 (npm run serve | npm run dev
) 来构建 node.js 前端 - 这将启动使用 Relay 管理应用程序状态的 React 应用程序的 webpack 服务器。您可以在 http://localhost:8083/ 上访问它。
要更新客户端模式,请访问 http://localhost:8083/ 并将响应(即当前 F# 服务器中的查询结果)复制粘贴到 data/schema.json 中。
流功能
现在 stream
指令具有额外的功能,例如基于间隔和/或批大小进行批处理(缓冲)。为了使其正常工作,必须在 SchemaConfig.Directives
列表中放置一个自定义流指令,该指令包含两个可选参数,分别称为 interval
和 preferredBatchSize
let customStreamDirective =
let args = [|
Define.Input(
"interval",
Nullable IntType,
defaultValue = Some 2000,
description = "An optional argument used to buffer stream results. ")
Define.Input(
"preferredBatchSize",
Nullable IntType,
defaultValue = None,
description = "An optional argument used to buffer stream results. ") |]
{ StreamDirective with Args = args }
let schemaConfig =
{ SchemaConfig.Default with
Directives = [
IncludeDirective
SkipDirective
DeferDirective
customStreamDirective
LiveDirective ] }
此样板代码可以轻松地用一个内置实现进行减少
let streamOptions =
{ Interval = Some 2000; PreferredBatchSize = None }
let schemaConfig =
SchemaConfig.DefaultWithBufferedStream(streamOptions)
实时查询
现在服务器组件支持 live
指令。为了支持实时查询,需要将模式的每种类型的每个字段配置为实时字段。这可以通过使用 ILiveFieldSubscription
和 ILiveQuerySubscriptionProvider
来完成,这两个都可以在 SchemaConfig
中进行配置
type ILiveFieldSubscription =
interface
abstract member Identity : obj -> obj
abstract member TypeName : string
abstract member FieldName : string
end
and ILiveFieldSubscription<'Object, 'Identity> =
interface
inherit ILiveFieldSubscription
abstract member Identity : 'Object -> 'Identity
end
and ILiveFieldSubscriptionProvider =
interface
abstract member HasSubscribers : string -> string -> bool
abstract member IsRegistered : string -> string -> bool
abstract member AsyncRegister : ILiveFieldSubscription -> Async<unit>
abstract member TryFind : string -> string -> ILiveFieldSubscription option
abstract member Add : obj -> string -> string -> IObservable<obj>
abstract member AsyncPublish<'T> : string -> string -> 'T -> Async<unit>
end
要将字段设置为实时字段,请调用 Register
扩展方法。每个订阅都需要知道对象身份,因此必须在 ILiveFieldSubscription
的 Identity 功能上进行配置。还需要传递 ObjectDef
内部的类型名称和字段名称
let schemaConfig = SchemaConfig.Default
let schema = Schema(root, config = schemaConfig)
let subscription =
{ Identity = fun (x : Human) -> x.Id
TypeName = "Hero"
FieldName = "name" }
schemaConfig.LiveFieldSubscriptionProvider.Register subscription
有了这个,英雄的字段名称现在可以实时更新了,当使用 live
指令查询时,它会被更新到客户端。要向订阅者推送更新,只需调用 Publish 方法,传递类型名称、字段名称和更新的对象
let updatedHero = { hero with Name = "Han Solo - Test" }
schemaConfig.LiveFieldSubscriptionProvider.Publish "Hero" "name" updatedHero
客户端提供者
我们的客户端库现在有一个完全重新设计过的类型提供者。要开始使用它,您首先需要访问您要连接的服务器的查询结果。可以通过提供者以以下两种方式之一实现:
- 提供所需 GraphQL 服务器(不需要任何自定义 HTTP 标头)的 URL。提供者将访问服务器,发送一个查询,并使用该模式提供用于执行查询的类型。
type MyProvider = GraphQLProvider<"http://some.graphqlserver.development.org">
- 为提供者提供 introspection json 文件。但请注意,introspection json 应该包含所有必需的字段。您可以通过在对所需服务器运行我们的标准查询并保存该文件到与使用提供者项目的文件夹相同的路径上[获取正确字段]。
type MyProvider = GraphQLProvider<"swapi_schema.json">
从现在起,您可以开始运行查询和突变
let operation =
MyProvider.Operation<"""query q {
hero (id: "1001") {
name
appearsIn
homePlanet
friends {
... on Human {
name
homePlanet
}
... on Droid {
name
primaryFunction
}
}
}
}""">()
// This is a instance of GraphQLProviderRuntimeContext.
// You can use it to provider a runtime URL to access your server,
// and optionally additional HTTP headers (auth headers, for example).
// If you use a local introspection file to parse the schema,
// The runtime context is mandatory.
let runtimeContext =
{ ServerUrl = "http://some.graphqlserver.production.org"
CustomHttpHeaders = None }
let result = operation.Run(runtimeContext)
// Query result objects have pretty-printing and structural equality.
printfn "Data: %A\n" result.Data
printfn "Errors: %A\n" result.Errors
printfn "Custom data: %A\n" result.CustomData
// Response from the server:
// Data: Some
// {Hero = Some
// {Name = Some "Darth Vader";
// AppearsIn = [|NewHope; Empire; Jedi|];
// HomePlanet = Some "Tatooine";
// Friends = [|Some {Name = Some "Wilhuff Tarkin";
// HomePlanet = <null>;}|];};}
// Errors: <null>
// Custom data: map [("documentId", 1221427401)]
有关如何使用客户端提供者的更多信息,请参阅示例文件夹。
中间件
您可以在 Executor<'Root>
对象之上创建和使用中间件。
使用 Executor 的查询执行过程包括三个阶段
架构编译阶段:当实例化
Executor<'Root>
类时会发生此阶段。在此阶段,使用类型的Schema映射来构建一个字段执行映射,它包含所有字段定义及其字段解析函数。此映射在规划阶段和执行阶段后面用于检索模式查询字段的值。操作规划阶段:在此阶段,执行一个没有执行计划的查询之前。此阶段负责分析查询生成的AST文档,并构建执行计划以执行此操作。
操作执行阶段:这是执行查询的阶段。它需要一个执行计划,所以通常在操作规划阶段之后发生。
所有阶段都将执行阶段所需的数据包装在一个上下文对象中。它们通过函数在内部分表示。
let internal compileSchema (ctx : SchemaCompileContext) : unit =
// ...
let internal planOperation (ctx: PlanningContext) : ExecutionPlan =
// ...
let internal executeOperation (ctx : ExecutionContext) : AsyncVal<GQLResponse> =
// ...
因此,在编译架构阶段,在SchemaCompileContext
对象内修改架构并生成执行映射。在操作规划阶段,使用PlanningContext
对象的值生成执行计划,最后将该计划与其他值一起传递到ExecutionContext
对象中,然后在操作执行阶段使用它们执行查询并生成GQLResponse
。
考虑到这一点,可以使用中间件来拦截每个阶段并按需自定义它们。每个中间件都必须作为一个特定签名的函数实现,并包裹在一个IExecutorMiddleware
接口中
type SchemaCompileMiddleware =
SchemaCompileContext -> (SchemaCompileContext -> unit) -> unit
type OperationPlanningMiddleware =
PlanningContext -> (PlanningContext -> ExecutionPlan) -> ExecutionPlan
type OperationExecutionMiddleware =
ExecutionContext -> (ExecutionContext -> AsyncVal<GQLResponse>) -> AsyncVal<GQLResponse>
type IExecutorMiddleware =
abstract CompileSchema : SchemaCompileMiddleware option
abstract PlanOperation : OperationPlanningMiddleware option
abstract ExecuteOperationAsync : OperationExecutionMiddleware option
可选的,为了易于实现,可以使用从其中派生出的具体类,它仅在构造函数中接收可选的子中间件函数
type ExecutorMiddleware(?compile, ?plan, ?execute) =
interface IExecutorMiddleware with
member _.CompileSchema = compile
member _.PlanOperation = plan
member _.ExecuteOperationAsync = execute
每个中间件函数 acting like an intercept function,有两个参数:阶段的上下文,下一个中间件的功能(或实际阶段本身,即最后运行),以及返回值。这些函数可以作为参数传递给Executor<'Root>
对象的构造函数
let middleware = [ ExecutorMiddleware(compileFn, planningFn, executionFn) ]
let executor = Executor(schema, middleware)
一个实际的中间件示例可以是测量规划查询所需时间的中间件。结果作为规划上下文的Metadata
部分返回。《Metadata》对象是一个Map<string, obj>
实现,它像一个携带要传递到每个阶段的信息的袋子,直到它作为GQLResponse
对象的一部分返回。您可以使用它通过中间件传递自定义信息。
let planningMiddleware (ctx : PlanningContext) (next : PlanningContext -> ExecutionPlan) =
let watch = Stopwatch()
watch.Start()
let result = next ctx
watch.Stop()
let metadata = result.Metadata.Add("planningTime", watch.ElapsedMilliseconds)
{ result with Metadata = metadata }
内置中间件
在FSharp.Data.GraphQL.Server.Middleware
软件包中有一些内置的中间件
QueryWeightMiddleware
此中间件可用于对架构的字段放置权重。这些加权字段现在可用于保护服务器免受可能在DDoS攻击中使用的复杂查询。
在定义字段时,我们使用扩展方法WithQueryWeight
来放置一个权重
let resolveFn (h : Human) =
h.Friends |> List.map getCharacter |> List.toSeq
let field =
Define.Field("friends", ListOf (Nullable CharacterType),
resolve = resolveFn).WithQueryWeight(0.5)
然后我们为Executor定义阈值中间件。如果我们执行一个递归方式请求"朋友的朋友"的查询,executor将在查询超过2.0的权重阈值之前只接受4次嵌套。
let middleware = [ Define.QueryWeightMiddleware(2.0) ]
ObjectListFilterMiddleware
此中间件可用于自动生成对象内部列表字段的过滤器。此过滤器可以作为查询的字段参数传递,并在解析函数的字段解析函数的ResolveFieldContext
参数中恢复。
例如,我们可以创建一个用于过滤Human
对象列表字段的中间件,这些字段的类型为Character option
let middleware = [ Define.ObjectListFilterMiddleware<Human, Character option>() ]
过滤器参数是一个对象,它通过字段上的filter
参数中的一个JSON定义进行映射。一个简单的示例是过滤出以字母A开头的英雄的朋友
query TestQuery {
hero(id:"1000") {
id
name
appearsIn
homePlanet
friends (filter : { name_starts_with: "A" }) {
id
name
}
}
}
此过滤器由中间件映射到ObjectListFilter
定义中
type FieldFilter<'Val> =
{ FieldName : string
Value : 'Val }
type ObjectListFilter =
| And of ObjectListFilter * ObjectListFilter
| Or of ObjectListFilter * ObjectListFilter
| Not of ObjectListFilter
| Equals of FieldFilter<System.IComparable>
| GreaterThan of FieldFilter<System.IComparable>
| LessThan of FieldFilter<System.IComparable>
| StartsWith of FieldFilter<string>
| EndsWith of FieldFilter<string>
| Contains of FieldFilter<string>
| FilterField of FieldFilter<ObjectListFilter>
查询中过滤器恢复的值可以在字段解析函数的ResolveFieldContext
中使用。为了方便访问,你可以使用扩展方法Filter
,它返回一个ObjectListFilter option
(如果没有实现中介件泛型定义的列表,或者没有提供过滤器输入,则该选项没有值)。
Define.Field("friends", ListOf (Nullable CharacterType),
resolve = fun ctx (d : Droid) ->
ctx.Filter |> printfn "Droid friends filter: %A"
d.Friends |> List.map getCharacter |> List.toSeq)
从字段解析上下文中检索此过滤器后,可以使用客户端代码自定义针对数据库运行的查询,例如,并扩展您的GraphQL API功能。
LiveQueryMiddleware
此中间件可用于快速允许您的模式字段能够使用live
指令进行查询,前提是所有字段都具有可由函数IdentityNameResolver
发现的标识属性名称。
/// A function that resolves an identity name for a schema object, based on a object definition of it.
type IdentityNameResolver = ObjectDef -> string
例如,如果我们的所有模式对象都具有名为Id
的标识字段,我们可以像这样使用我们的中间件
let schema = Schema(query = queryType)
let middleware = [ Define.LiveQueryMiddleware(fun _ -> "Id") ]
let executor = Executor(schema, middleware)
IdentityNameResolver
是可选的。如果没有提供解析函数,则使用此默认实现。此外,还必须通过如上所述的Publish
的ILiveFieldSubscriptionProvider
进行订阅者的通知。
使用扩展来构建自己的中间件
您可以使用FSharp.Data.GraphQL.Shared
包提供的扩展方法来帮助构建自己的中间件。在制作中间件时,通常需要修改架构定义以向由用户代码定义的架构添加功能。ObjectListFilter
中间件是一个示例,其中所有实现特定类型列表的字段都需要修改,通过接受一个名为filter
的参数。
因为字段定义默认是不可变的,有时生成具有改进功能的副本会很费劲。这就是扩展方法可以帮助的地方:例如,如果您需要在模式编译阶段内部现有的字段添加一个参数,您可以使用FieldDef<'Val>
接口的WithArgs
方法。
let field : FieldDef<'Val> = // Search for field inside ISchema
let arg : Define.Input("id", StringType)
let fieldWithArg = field.WithArgs([ arg ])
要查看用于增强定义的完整扩展列表,您可以查看包含在FSharp.Data.GraphQL.Shared
包中的TypeSystemExtensions
模块。
产品 | 版本 兼容和附加计算的面向目标框架版本。 |
---|---|
.NET | net5.0 已计算。 net5.0-windows 已计算。 net6.0 已计算。 net6.0-android 已计算。 net6.0-ios 已计算。 net6.0-maccatalyst 已计算。 net6.0-macos 已计算。 net6.0-tvos 已计算。 net6.0-windows 已计算。 net7.0 已计算。 net7.0-android 已计算。 net7.0-ios 已计算。 net7.0-maccatalyst 已计算。 net7.0-macos 已计算。 net7.0-tvos 已计算。 net7.0-windows 已计算。 net8.0 已计算。 net8.0-android 已计算。 net8.0-browser 已计算。 net8.0-ios 已计算。 net8.0-maccatalyst 已计算。 net8.0-macos 已计算。 net8.0-tvos 已计算。 net8.0-windows 已计算。 |
.NET Core | netcoreapp2.0 已计算。 netcoreapp2.1 已计算。 netcoreapp2.2 已计算。 netcoreapp3.0 已计算。 netcoreapp3.1 已计算。 |
.NET Standard | netstandard2.0 兼容。 netstandard2.1 已计算。 |
.NET Framework | net461 已计算。 net462 已计算。 net463 已计算。 net47 已计算。 net471 已计算。 net472 已计算。 net48 已计算。 net481 已计算。 |
MonoAndroid | monoandroid 已计算。 |
MonoMac | monomac 已计算。 |
MonoTouch | monotouch 已计算。 |
Tizen | tizen40 已计算。 tizen60 已计算。 |
Xamarin.iOS | xamarinios 已计算。 |
Xamarin.Mac | xamarinmac 已计算。 |
Xamarin.TVOS | xamarintvos 已计算。 |
Xamarin.WatchOS | xamarinwatchos 已计算。 |
-
.NETStandard 2.0
- FSharp.Core (>= 7.0.403)
- FSharp.Data.GraphQL.Shared (>= 2.2.1)
- Microsoft.Extensions.Http (>= 7.0.0)
NuGet 包
此包未由任何 NuGet 包使用。
GitHub 仓库
此包未由任何流行的 GitHub 仓库使用。
版本 | 下载 | 最后更新 |
---|---|---|
2.2.1 | 1,609 | 6/16/2024 |
2.2.0 | 275 | 5/8/2024 |
2.1.0 | 100 | 4/21/2024 |
2.0.0 | 110 | 3/24/2024 |
2.0.0-beta1 | 66 | 2/16/2024 |
1.0.7 | 7,660 | 12/30/2020 |
1.0.6 | 565 | 12/17/2020 |
1.0.5 | 2,317 | 3/24/2020 |
1.0.4 | 638 | 3/22/2020 |
1.0.3 | 834 | 3/3/2020 |
1.0.2 | 1,090 | 8/20/2019 |
1.0.1 | 1,052 | 7/10/2019 |
1.0.0 | 632 | 7/5/2019 |
1.0.0-beta9 | 500 | 5/22/2019 |
1.0.0-beta8 | 504 | 5/22/2019 |
1.0.0-beta7 | 492 | 5/18/2019 |
1.0.0-beta6 | 483 | 5/17/2019 |
1.0.0-beta5 | 496 | 5/15/2019 |
1.0.0-beta4 | 448 | 5/13/2019 |
1.0.0-beta3 | 516 | 5/10/2019 |
1.0.0-beta2 | 483 | 5/8/2019 |
1.0.0-beta | 488 | 4/28/2019 |
0.0.18-beta | 601 | 1/17/2019 |
0.0.17-beta | 591 | 1/11/2019 |
0.0.16-beta | 592 | 1/4/2019 |
0.0.15-beta | 607 | 1/3/2019 |
0.0.14-beta | 616 | 12/9/2018 |
0.0.13-beta | 619 | 12/3/2018 |
0.0.12-beta | 621 | 11/21/2018 |
0.0.11-beta | 596 | 11/21/2018 |
0.0.10-beta | 603 | 11/14/2018 |
0.0.8-beta01 | 617 | 10/28/2018 |
0.0.8-beta | 592 | 10/28/2018 |
0.0.7-beta | 694 | 9/17/2018 |
0.0.6-beta | 721 | 8/10/2018 |
0.0.5-beta | 683 | 8/10/2018 |
0.0.4-beta01 | 902 | 5/31/2018 |
0.0.3-beta | 747 | 3/22/2018 |
0.0.2-beta | 994 | 9/30/2016 |
0.0.1-beta | 1,492 | 7/28/2016 |