FSharp.Data.GraphQL.Server.Middleware 2.2.1

dotnet add package FSharp.Data.GraphQL.Server.Middleware --version 2.2.1                
NuGet\Install-Package FSharp.Data.GraphQL.Server.Middleware -Version 2.2.1                
旨在在 Visual Studio 的包管理器控制台中使用此命令,因为它使用了 NuGet 模块的 Install-Package 版本。
<PackageReference Include="FSharp.Data.GraphQL.Server.Middleware" Version="2.2.1" />                
对于支持 PackageReference 的项目,将此 XML 节点复制到项目文件中以引用该包。
paket add FSharp.Data.GraphQL.Server.Middleware --version 2.2.1                
#r "nuget: FSharp.Data.GraphQL.Server.Middleware, 2.2.1"                
#r 指令可用于 F# Interactive 和 Polyglot Notebooks。将此复制到交互工具或脚本的源代码中,以引用包。
// Install FSharp.Data.GraphQL.Server.Middleware as a Cake Addin
#addin nuget:?package=FSharp.Data.GraphQL.Server.Middleware&version=2.2.1

// Install FSharp.Data.GraphQL.Server.Middleware as a Cake Tool
#tool nuget:?package=FSharp.Data.GraphQL.Server.Middleware&version=2.2.1                

FSharp.Data.GraphQL

F# 实现 Facebook GraphQL 查询语言规范

Publish to GitHub Publish to NuGet

Join the chat at https://gitter.im/FSharp-Data-GraphQL/community

安装项目模板

输入以下命令以安装创建 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 订阅) 的使用

请参阅 AspNetCore/README.md

演示

GraphiQL 客户端

前往GraphiQL 示例目录。要运行它,请以调试设置构建并运行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 和兼容 Relay 服务器 API 的示例应用程序。

要运行它,请使用调试设置构建 FSharp.Data.GraphQLFSharp.Data.GraphQL.Relay 项目。然后通过在 FSI 中运行 server.fsx 脚本来启动服务器,这将启动一个在端口 8083 上运行的兼容 Relay 的 F# 服务器。然后通过获取所有依赖项(运行npm i)并运行它来构建 node.js 前端(运行npm run serve | npm run dev) - 这将启动一个运行 Relay 的 webpack 服务器来管理应用程序状态。您可以在http://localhost:8083/ 上访问它。

要更新客户端模式,请访问http://localhost:8083/,并将响应(即当前 F# 服务器的 introspection 查询结果)复制粘贴到data/schema.json中。

流特性

stream指令现在具有附加功能,例如按间隔和/或批次大小进行批处理(缓冲)。为了使其正常工作,必须在SchemaConfig.Directives列表中放置一个自定义流指令,该指令包含两个可选参数,分别称为intervalpreferredBatchSize

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指令。为了支持实时查询,每个类型的每个字段都需要配置为实时字段。这是通过使用ILiveFieldSubscriptionILiveQuerySubscriptionProvider来完成的,它们可以在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

客户端提供者

我们的客户端库现在有一个完全重新设计的类型提供者。要开始使用它,您首先需要访问您尝试连接的服务器的 introspection 模式。这可以通过以下两种方式之一来完成

  1. 提供所期望的 GraphQL 服务的 URL(无需任何自定义 HTTP 头)。提供者将访问服务,发送 Introspection 查询,并使用该模式提供用于进行查询的类型。
type MyProvider = GraphQLProvider<"http://some.graphqlserver.development.org">
  1. 为供应商提供用于的 introspection json 文件。但请注意,introspection json 必须包含供应商所需的全部字段。您可以通过在目标服务器上运行 我们的标准 introspection 查询 来获取正确的字段,然后将它们保存到与使用供应商的项目相同路径的文件中。
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> 类实例化时发生此阶段。在这个阶段,类型模式映射用于构建一个字段执行映射,其中包含所有字段定义及其字段解析函数。此映射在规划阶段和执行阶段后续过程中用于检索模式查询字段的值。

  • 操作规划阶段:在运行没有执行计划的查询之前发生此阶段。此阶段负责分析查询生成的 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

每个中间件函数都类似于拦截函数,有两个参数:阶段上下文、下一个中间件(或实际阶段,它是最后一个运行的),以及返回值。这些函数可以作为参数传递给 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是可选的。如果没有提供解析函数,则使用此默认实现。另外,通知订阅者必须通过ILiveFieldSubscriptionProviderPublish,如上所述。

使用扩展来构建自己的中间件

您可以使用由FSharp.Data.GraphQL.Shared包提供的扩展方法来帮助构建自己的中间件。在创建中间件时,您通常需要修改模式定义以向用户代码定义的模式添加功能。《ObjectListFilter》中间件是一个示例,其中需要修改实现特定类型列表的所有字段,通过接受一个名为filter的参数。

由于字段定义默认为不可变,因此在编译阶段向已定义的(schema)字段添加参数可能是一项艰巨的任务。这就是扩展方法可以提供帮助的地方:例如,如果您需要在编译阶段向已定义的字段添加一个参数,则可以使用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 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标准的信息。

NuGet软件包

此包未使用任何NuGet软件包。

GitHub仓库

此包未使用任何流行的GitHub仓库。

版本 下载 最后更新
2.2.1 123 6/16/2024
2.2.0 136 5/8/2024
2.1.0 92 4/21/2024
2.0.0 145 3/24/2024
2.0.0-beta1 223 2/16/2024
1.0.7 7,744 12/30/2020
1.0.6 977 12/17/2020
1.0.5 2,140 3/24/2020
1.0.4 1,046 3/22/2020
1.0.3 1,133 3/3/2020
1.0.2 1,054 8/20/2019
1.0.1 1,143 7/10/2019
1.0.0 1,061 7/5/2019